Compare commits

...

1027 Commits

Author SHA1 Message Date
78dcd3799b sync: do not require python 3.9
Use pre-3.9 syntax for NamedTuple, so that users do not need to have
python 3.9 or later installed.

Bug: b/255632143, crbug.com/gerrit/16355
Test: manually verified with python 3.8
Change-Id: I488d2d5267ed98d5c55c233cc789e629f1911c9d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349395
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2022-10-25 22:46:47 +00:00
acc4c857a0 sync: only use --cruft when git supports it.
git gc --cruft was added in 2.37.0.

Bug: https://crbug.com/gerrit/16270
Change-Id: I71e46741e33472a92f16d6f11c51a23e1e55d869
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/346577
Reviewed-by: Emily Shaffer <emilyshaffer@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-22 19:18:48 +00:00
a39af3d432 project: Add a missing call to _CopyAndLinkFiles
If a file that is copied using a <copyfile> tag is modified and not
committed or if it is committed to a detached head, then running `repo
sync` would update the target file as expected. However, if the
modified file is committed to a local branch, then running `repo sync'
would not update the target file as expected.

Change-Id: Ic98e37d1c2e51fd1bf15abf149c7d06190cfd6d2
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/344475
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-20 09:24:01 +00:00
4cdfdb7734 manifest: allow extend-project to override dest-branch and upstream
Bug: https://crbug.com/gerrit/16238
Change-Id: Id6eff34791525b3df690e160c911c0286331984b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/345144
Tested-by: Erik Elmeke <erik@haleytek.corp-partner.google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-20 04:25:02 +00:00
1eddca8476 sync: use namedtuples for internal return values
Replace tuple returns with namedtuples, to simplify adding new fields.

Extend the Sync_NetworkHalf return value to:
 - success: True if successful (the former return value)
 - remote_fetched: True if we called `git fetch`

Change-Id: If63c24c2f849523f77fa19c05bbf23a5e9a20ba9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/344534
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-09-19 22:03:18 +00:00
aefa4d3a29 sync: incorporate review feedback.
This incorporates feedback from
https://gerrit-review.googlesource.com/c/git-repo/+/345114

Change-Id: I04433d6435b967858f1ffb355217d90bc48c1e5d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/345894
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-19 22:03:18 +00:00
4ba29c42ca diffmanifests: Handle Missing Projects in Repo Workspace
By default there are 4 categories in the diffmanifests
api puts the diffs in to - added, removed, changed and unreachable

Example of command - repo diffmanifests 1.xml 2.xml

added - list down the projects present in second manifest but not in
first
removed - list down the projects present in first but not in
second
changed - list down the changes and the differences for each project
unreachable - when it encounters revision value in a project is incorrect

But, when there are projects present in both manifests and could not
find in local workspace where we have cloned the repo(because of
different/subset manifest xml) - this will create unhandled exception

Now we have added a 5th category called 'missing' - where in such
cases it will handle the scenario and print the log for user

Example:
added projects :
        project_2 at revision e6c8a59832c05dc4b6a68cee6bc0feb832181725

removed projects :
        project_1 at revision e6c8a59832c05dc4b6a68cee6bc0feb832181725

changed projects :
        project_3 changed from 3bb890e1286f04e84d505e5db48e0ada89892331 to e434b3736f11537c67590fefadfe4495895e9785

missing projects :
        project_4

Change-Id: I244e8389bff7e95664c29d3dcb61e22308e3a573
Signed-off-by: Shashank Devaraj <shashankkarthik@gmail.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/344774
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-09-15 17:42:08 +00:00
45ef9011c2 update-manpages: force use of active interp
Since the repo wrapper uses #!/usr/bin/python, use the python3 that
this wrapper is actively using.

Change-Id: I03d1e54418d18a504eec628e549b4cc233621c45
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/345294
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-09-12 19:27:09 +00:00
891e8f72ce sync: save any cruft after calling git gc.
This is ENABLED BY DEFAULT due to data corruption potential.  To disable
it, set REPO_BACKUP_OBJECTS=0 in the environment.

While the workspace will grow over time, this provides a recovery path
for an issue where objects are erroneously deleted from the workspace,
resulting in lost work.  Once the root cause is determined, we will be
able to stop saving backups again.

Backups are kept in .git/objects/.repo/pack.bak

Bug: https://crbug.com/gerrit/16247
Change-Id: Ib8b5c9b4bf0dfa9e29606e0f5c881d65996b2a40
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/345114
Reviewed-by: Jonathan Tan <jonathantanmy@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-09-09 19:04:30 +00:00
af8fb132d5 Revert "project: initialize new manifests in temp dirs"
This reverts commit 07d21e6bde.

Reason for revert: crbug.com/gerrit/16230, b/244467766 - breaks aosp-master-with-phones case

Change-Id: Id967d92f8622c2c13356b09e46ece9f20040aabc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/344314
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-09-08 23:28:27 +00:00
4112c07688 sync: Correctly sync multi manifest workspaces
When actually fetching the manifests, start at the correct (sub)
manifest.

Bug: https://crbug.com/gerrit/16198
Change-Id: I39fdd726f1917ef4277a0b7c83663c8f49167466
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343914
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-08-24 19:35:38 +00:00
fbd5dd3a30 manifest_xml: improve topdir accuracy.
Do not include a trailing path separator when submanifest_path is empty.

Bug: https://crbug.com/gerrit/16104
Change-Id: Ia65e355de25bdb1067fe50ab1d47db6e798d5a71
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343674
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-08-22 21:49:49 +00:00
3d27c71dd9 init: hide identify spam when reinitializing
We don't want to keep showing the user config notice when reinitializing
existing checkouts, so hide it.

Change-Id: Id40610bd683396cbff7e1aefc092c8b77c397783
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343536
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-08-22 21:01:35 +00:00
488d54d4ee init: show a notice when reinitializing
Make it clear to users when we're reinitializing an existing checkout
in case they weren't expecting it.

Bug: https://crbug.com/gerrit/12396
Change-Id: I22e89ae041a8e7b147c9d06a82f1302dd5807ae0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343535
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-22 21:01:10 +00:00
5a5cfce1b2 stage: add missing flush before project prompt
Bug: https://crbug.com/gerrit/13223
Change-Id: Ib279d86a52e1035e02d6f7d8f053c3a43e721032
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343555
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-22 20:48:29 +00:00
e6d4b84060 upload: respect --yes with large upload confirmation
If the user passes in --yes, don't prompt them to confirm large uploads.

Bug: https://crbug.com/gerrit/14085
Change-Id: Ic801b21be80ba181801531acd4af5057ec10c11c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343554
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-22 20:48:27 +00:00
d75ca2eb9d launcher: make missing .repo/repo/repo an error
If the specified repo dir doesn't actually have a `repo` program,
we only show a warning before continuing on, and then we fail in
weird ways.  Since we really need the repo dir to contain repo,
have this be fatal and delete the results.

Bug: https://crbug.com/gerrit/13526
Change-Id: Icee4cba96136d470cbb459a81918c40205078f98
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343538
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-22 20:48:02 +00:00
a010a9f4a0 launcher: initialize repo in a temp dir
In case something goes wrong in the initial setup of the repo dir,
clone it into a temporary .repo/repo.tmp/ directory first, and then
rename it only when things have finished fully.

Bug: https://crbug.com/gerrit/13526
Change-Id: Ib0f5a975e4d436b0fb616fac70f5789c4e02a61a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343537
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-22 20:47:50 +00:00
8a54a7eac3 cherry-pick: tighten up output
If stdout or stderr are empty, don't print empty lines.
Also trim any trailing lines so we don't show excess ones.

Change-Id: I65fcea191e1f725be03c064cb510578632466013
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343516
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-08-22 19:38:32 +00:00
63a5657ecf git_command: fix input passing
After reworking this function to use subprocess for output capturing
in commit c87c1863b1 ("git_command:
switch process capturing over to subprocess"), passing input via
stdin write no longer works.  We have to pass it via communicate(),
and we have to pass it a string instead of bytes (since we always
use encoding='utf-8' now).  This is fine since the only user of the
input= setting today is already passing in a string.

Bug: https://crbug.com/gerrit/16151
Change-Id: Ic58db1e568b8f8aa840a6d62c5a157c14aa6d9bc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343515
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-08-22 19:34:59 +00:00
07d21e6bde project: initialize new manifests in temp dirs
If initializing the manifest fails for any reason, don't leave it in
a half complete state.  This can cause problems if/when the user tries
to reinit because different codepaths will be taken.  For example, if
we initialize manifests.git and don't finish probing the remote to see
what default branch it uses, we end up always using "master" even if
that isn't what the remote uses.

To avoid all of this, use .tmp dirs when initializing, and rename to
the final path only after we complete all the right steps.

We should roll this out to all projects we clone, but start with the
manifest project for now.

Bug: https://crbug.com/gerrit/13526
Bug: https://crbug.com/gerrit/15805
Change-Id: I0214338de69ee11e090285c6b0b211052804af06
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343539
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-22 19:34:46 +00:00
076d54652e init: change --depth default to 1 for manifest repo
People rarely care about the history of the manifest repo.  Change
the default to 1 to speed up initial setup.  If people really want
the full history, they can pass --manifest-depth=0.

We can't reuse the existing --depth option because that applies to
all projects we clone, not just the manifest repo.

Bug: https://crbug.com/gerrit/16193
Change-Id: Ideab1712e9ffc743b9bae97903e074d7d429b134
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343435
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-08-18 19:12:21 +00:00
790f4cea7a add a few more docs to existing funcs
Change-Id: I27317a59aba67c05ca1fd333e8f064c0edccb209
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343185
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-18 17:31:01 +00:00
39cb17f7a3 init: use --current-branch by default
People rarely care about having all manifest branches locally.  Change
the default to only pull down the selected branch.  If people want other
branches, the -b option will fetch it automatically, or people can use
--no-current-branch.

This only applies to the manifest project syncing, not the rest of the
projects that are in the checkout.

Bug: https://crbug.com/gerrit/16194
Change-Id: Ia9e7e2f23b8028d82772db72dcc7d6c32260be79
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343434
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-08-18 16:54:17 +00:00
ad1b7bd2e2 start: do not swallow git output all the time
Normally git produces no output when creating or switching branches.
If there's a problem though, we want to show that to the user.  So
switch from capturing all output to running in quiet mode.

Bug: https://crbug.com/gerrit/15819
Change-Id: I7873ecc7c3bacce591899cc9471cb0244eb74541
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343454
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-18 16:43:39 +00:00
3c2d807905 pager: catch startup failures on Windows
If the user's pager settings are broken, display an error message
rather than crash to avoid confusing them.

Bug: https://crbug.com/gerrit/16173
Change-Id: Idc0891da783c68f3a96ac53a82781e34e40421fb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343437
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-18 16:43:16 +00:00
7fa8eedd8f upload: add --push-options tips & doc link
Change-Id: Iee38a80974c53231d1e9f04f7f85b2d0bac96dbb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/342354
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-08-18 16:40:40 +00:00
dede564c3d project: simplify GetRemote a bit
We almost always use self.remote.name when calling self.GetRemote,
so make that the default to simplify the code a bit.

Change-Id: Ifdf6e1370d6b8963b44e6d384b0fac8fa5c4f2ba
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/343184
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-08-17 16:54:21 +00:00
ac76fd3e3a upload: Add ready flag to remove wip
The `--wip` allow to bulk push changed as work-in-progress. This CL
intend to allow the opposite opperation by removing the wip mark on the
CL and set it to be ready to review

Change-Id: If0743c5b14829f77be2def5a8547060d06a5648c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/342214
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: William Escande <wescande@google.com>
2022-08-03 20:17:06 +00:00
a8c34d1075 commit-msg: Sync commit-msg from gerrit 3.6.1
This includes:
- Ignore squash commits.
- Update to hash generation.
- Update to Change-Id and trailer generation.

TEST=cp hooks/commit-msg .git/hooks/commit-msg
TEST=git commit -s # right order
TEST=git commit; git commit --amend -s # right order

Change-Id: I4e4a2a02905d330f2863b562d7914fe6567a4118
Signed-off-by: Evan Benn <evanbenn@chromium.org>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/339554
Tested-by: Evan Benn <evanbenn@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-07-29 01:29:19 +00:00
5951e3043f sync: handle smartsync HTML responses better
If the server responds with an HTML page, we should show that to the
user instead of crashing with XML errors.

Bug: https://crbug.com/gerrit/15936
Change-Id: I52e6b781c3bb6a6c9f6ecbe2e0907044876cdc8d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/337519
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-07-28 17:44:21 +00:00
48ea25c6a7 sync: start clearing git settings that disabled pruning
For projects that no longer share their per-project objects directly, we
no longer have to disable the git settings that disable pruning.  See
commit "project: stop directly sharing objects/ between shared projects"
for more details.

Bug: https://crbug.com/gerrit/15553
Change-Id: Ica0e83c3002716424c2bc9111b3b3d3a76c30973
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/337535
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-07-25 17:13:20 +00:00
355f4398d8 sync: rework --jobs to provide better defaults
For --jobs-network, the logic is now:
* If the user specifies --jobs-network, use that.
* Else, if the user specifies --jobs, use that.
* Else, if the manifest specifies sync-j, use that.
* Else, default to 1.
Then we limit the jobs count based on the softlimit RLIMIT_NOFILE.

For --jobs-checkout, the logic is now:
* If the user specifies --jobs-checkout, use that.
* Else, if the user specifies --jobs, use that.
* Else, if the manifest specifies sync-j, use that.
* Else, default to DEFAULT_LOCAL_JOBS which is based on user's ncpus.
Then we limit the jobs count based on the softlimit RLIMIT_NOFILE.

For garbage collecting, the logic is now:
* If the user specifies --jobs, use that.
* Else, if the manifest specifies sync-j, use that.
* Else, default to the user's ncpus.
Then we limit the jobs count based on the softlimit RLIMIT_NOFILE.

Having to factor in the manifest settings makes this more complicated
which is why we delay processing of defaults until after we've synced
the manifest projects.

Bug: http://b/239712300
Change-Id: Id27cda63c76c156f1d63f6a20cb2c4ceeb3d547c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/341394
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-07-25 15:36:43 +00:00
bddc964d93 Fix the printed path of the ".repo" dir after a fresh init.
Apparently, manifest.topdir already contains a trailing slash in some
cases, so a simple string concatenation may or not lead to double
slashes. It is safer to use os.path.join. See
https://screenshot.googleplex.com/6pSY3QewAeCdAqk

Change-Id: I2411452296b7e78fc975787b675273a48d6b3d85
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/341574
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mateus Azis <azis@google.com>
2022-07-25 15:22:14 +00:00
a8cf575d68 Omit local_manifest groups from superproject override.
When we create superproject_override.xml, do not include projects that
are present from local_manifests/*.  Such projects are fully under the
control of the local_manifests/ file.

Bug: b/238934278
Test: manual, ./run_tests
Change-Id: I40382ceb82d9cf7b8dc7b5f2abed3f6d4d80017e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/340877
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Reviewed-by: Sam Saccone 🐐 <samccone@google.com>
2022-07-15 23:32:24 +00:00
8501d4602a status, diff: display correct path for multi-manifest
Display the project path relative to the outermost manifest by default,
and relative to the sub manifest only when --this-manifest-only is
specified.

For project-related diagnostic messages, use the outermost manifest for
messages.

Change-Id: I4537d7dd412a2c182e77d6720e95c1b0ef70eb0e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/340754
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-07-14 16:00:18 +00:00
8db78c7d4d project: simplify if-statement
Change-Id: I05e4505b45963fe6e85cf74a669afafd00fc83c0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/340457
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Martin Geisler <mgeisler@google.com>
2022-07-11 17:58:06 +00:00
9fb64ae29c upload: add ‘--ignore-untracked-files’ option
This option will suppress the

    Uncommitted changes in ... (did you forget to amend?)

prompt when there are untracked (unknown) files in the working copy.
The prompt is still shown if tracked files are modified.

Change-Id: Ia3fcc82989b7fad09b69214eda31e2d0dfc14600
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/340456
Tested-by: Martin Geisler <mgeisler@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-07-11 17:57:43 +00:00
d47d9ff1cb man: regenerate
Change-Id: I3ca8ca8f502605b194ebe65b315eda08c51592a6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/340494
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-07-11 16:42:23 +00:00
68d69635c7 Fix Projects.shareable_dirs
If this tree is not using alternates for object sharing, then we need to
continue to call it a shared directory.

Bug: https://bugs.chromium.org/p/gerrit/issues/detail?id=15982
Test: manual
Change-Id: I1750f10b192504ac67f552222f8ddb9809d344fe
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/338974
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-06-08 16:49:08 +00:00
ff6b1dae1e Only sync superproject if it will be used.
If the user says `--no-use-superproject`, then do not bother syncing the
superproject.

Also add/update docstrings and comments throughout.

Change-Id: I9cdad706130501bab9a22d3099a1dae605e9c194
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/338975
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-06-08 16:49:08 +00:00
bdcba7dc36 sync: add multi-manifest support
With this change, partial syncs (sync with a project list) are again
supported.

If the updated manifest includes new sub manifests, download them
inheriting options from the parent manifestProject.

Change-Id: Id952f85df2e26d34e38b251973be26434443ff56
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334819
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-05-26 00:03:37 +00:00
1d00a7e2ae project: initial separation of shared project objects
For now, this is opt-in via environment variables:
  - export REPO_USE_ALTERNATES=1

The shared project logic that shares the internal .git/objects/ dir
directly between multiple projects via the project-objects/ tree has
a lot of KI with random corruption.  It all boils down to projects
sharing objects/ but not refs/.  Git operations that use refs to see
what objects are reachable and discard the rest can easily discard
objects that are used by other projects.

Consider this project layout:
<show fs layout>

There are unique refs in each of these trees that are not visible in
the others.  This means it's not safe to run basic operations like
git prune or git gc.

Since we can't share refs (each project needs to have unique refs
like HEAD in order to function), let's change how we share objects.
The old way involved symlinking .git/objects/ to the project-objects
tree.  The new way shares objects using git's info/alternates.

This means project-objects/ will only contain objects that exist in
the remote project.  Local per-project objects (like when creating
branches and making changes) will never be shared.  When running a
prune or gc operation in the per-project state, it will only ever
repack or discard those per-project objects.  The common shared
objects would only be cleaned up when running a common operation
(i.e. by repo itself).

One downside to this for users is if they try blending unrelated
upstream projects.  For example, in CrOS we have multiple kernel
projects (for diff versions) checked out.  If a dev fetched the
upstream Linus tree into one of them, the objects & tags would
not be shared with the others, so they would have to fetch the
upstream state for each project.  Annoying, but better than the
current corruption situation we're in now.

Also if the dev runs a manual `git fetch` in the per-project to
sync it up to newer state than the last `repo sync` they ran,
the objects would get duplicated.  However, git operations later
on should eventually dedupe this.

Bug: https://crbug.com/gerrit/15553
Change-Id: I313a9b8962f9d439ef98ac0ed37ecfb9e0b3864e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328101
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-05-26 00:02:18 +00:00
3a0a145b0e upload: move label validation to core function
This way we know we don't need to encode the labels.

Change-Id: Ib83ed8f4ed05f00b9d2d06a9dd3f304e4443430e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/337518
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-05-21 19:19:44 +00:00
74737da1ab tests: switch to tempfile.TemporaryDirectory
Now that we don't need to support Python 2, we can switch to this
API for better contextmanager logic.

Change-Id: I2d03e391121886547e7808a3b5c3b470c411533f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/337515
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-05-20 11:38:10 +00:00
0ddb677611 project: fix --use-superproject logic for init.
If init was run with --use-superproject, init failed.

If init was run without --{no,}use-superproject option then manifests
with <superproject/> elements were mishandled.

Bug: b/233226285
Test: manual
Change-Id: I737e71c89d2d7c324114f58bf2dc82b40e5beba7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/337534
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-05-20 11:01:28 +00:00
501733c2ab manifest: add submanifest.default_groups attribute
When the user does not specify any manifest groups, this allows the
parent manifest to indicate which manifest groups should be used for
syncing the submanifest.

Change-Id: I88806ed35013d13dd2ab3cd245fcd4f9061112c4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/335474
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-29 18:42:23 +00:00
0165e20fcc project: Do not exit early on --standalone-manifest.
After we successfully download the standalone manifest file, we cannot
exit early.

Bug: https://bugs.chromium.org/p/gerrit/issues/detail?id=15861
Change-Id: Ic47c9f7e9921851f94c6f24fd82b896eff524037
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/335974
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-29 17:13:49 +00:00
0de4fc3001 project: Add missing imports
Some imports were missed when moving manifestProject to project.py

Bug: https://bugs.chromium.org/p/gerrit/issues/detail?id=15861
Change-Id: Id8fffeaa3f88f344a13b5ab44e5403c7edd98f31
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/335554
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2022-04-21 18:44:26 +00:00
4c11aebeb9 progress: optimize progress bar updates a bit
Rather than erase the entire line first then print out the new content,
print out the new content on top of the old and then erase anything we
didn't update.  This should result in a lot less flashing with faster
terminals.

Bug: https://crbug.com/gerrit/11293
Change-Id: Ie2920b0bf3d5e6f920b8631a1c406444b23cd12d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/335214
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-04-19 23:50:48 +00:00
b90a422ab6 Override the manifest for the entire command
When a manifest file is overridden, remember that and keep using the
override for the remainder of the process.  If we need to revert it,
make the override name evaluate False.

Change-Id: I1eee05fec6988c1ee4a3c751c4b540d5b5d11797
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/335136
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-19 21:28:20 +00:00
a46047a822 sync: refactor use of self.manifest
We need to iterate over multiple manifests, and generally use the
outer_client.manifest for multi-manifest support.  This refactors the
use of self.manifest into a chosen manifest.

Change-Id: I992f21d610c929675e99555ece9c38df4b635839
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334699
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-14 22:24:04 +00:00
5fa912b0d1 Stop passing optparse.Values to git_superproject
Make git_superproject independent of the command line by passing
the specific value instead of requiring the caller to have an
optparse.Values object to pass in.

Flag --use-superproject and --archive as incompatible in subcmds/init.py

Change-Id: Ied7c874b312e151038df903c8af4328f070f387c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/335135
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-14 22:23:16 +00:00
4ada043dc0 ManifestProject: add manifest_platform
And fix most of the other attributes to return the value instead of
None.

Change-Id: Iddcbbeb56238ee082bb1cae30adbd27a2f551f3d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/335134
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-04-14 20:56:45 +00:00
d8de29c447 forall: fix multi-manifest variables.
- REPO_PATH is relative to the root of the client. REPO_OUTERPATH is not
  needed.
- REPO_INNERPATH is relative to the sub manifest root.
- REPO_OUTERPATH is the path for the sub manifest root relative to the
  root of the client.

Change-Id: I031692891cfef2634d1358584d27a6a4df735c20
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334899
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-04-14 14:31:47 +00:00
2cc3ab7663 git_superproject: only print beta notice once.
This eliminates duplicate notices during multi-manifest syncs.

Change-Id: Idcb038ddeb363368637c58c11346ebf8fd2b27ac
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334939
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-04-14 00:07:25 +00:00
d56e2eb421 manifest_xml: use Superproject to hold XML content
Always create Superproject when there is a <superproject> tag, and have
it hold the XML content, similar to how other manifest elements are
handled.

This also adds SetQuiet and SetPrintMessages to Superproject
consistent with manifest.SetUseLocalManifests.

Change-Id: I522bf3da542006575799f0640c67f7052704f266
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334641
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-04-12 15:46:23 +00:00
d52ca421d5 sync: respect sync-c manifest option
The documentation states that a `sync-c` attribute in the manifest file
can set a default for whether only the current branch should be fetched
or all branches. This seems to have been broken for some time.

Commit 7356114 introduced the `--no-current-branch` CLI option and
relied on getting `None` via `optparse` if neither `--current-branch`
nor `--no-current-branch` was set to distinguish it from a boolean
value. If `None` was received, it would read the value from the manifest
option `sync-c`. The parsing went through the utility function
`_GetCurrentBranchOnly` which returned `True` if `--current-branch` had
been given on the command-line, or fell back on the "superproject"
setting, which would either return `True` or `None`. This would
incorrectly make `repo` fall back to the manifest setting even if the
user had given `--no-current-branch` if no superproject was requested --
the manifest became "too powerful":

Command-line         Using superproject  → `current_branch_only`
------------         ------------------  -----------------------
                     No                  From manifest
                     Yes                 True
--current-branch     No                  True
--current-branch     Yes                 True
--no-current-branch  No                  From manifest ← wrong
--no-current-branch  Yes                 True

In commit 0cb6e92 the superproject configuration value reading changed
from something that could return `None` to something that always
returned a boolean. If it returned `False`, this would then incorrectly
make `repo` ignore the manifest option even if neither
`--current-branch` nor `--no-current-branch` had been given. The
manifest default became useless:

Command-line         Using superproject  → `current_branch_only`
------------         ------------------  -----------------------
                     No                  False ← wrong
                     Yes                 True
--current-branch     No                  True
--current-branch     Yes                 True
--no-current-branch  No                  False
--no-current-branch  Yes                 True

By swapping the order in which the command-line option target and the
superproject setting is evaluated, things should work as documented:

Command-line         Using superproject  → `current_branch_only`
------------         ------------------  -----------------------
                     No                  From manifest
                     Yes                 True
--current-branch     No                  True
--current-branch     Yes                 True
--no-current-branch  No                  False
--no-current-branch  Yes                 True

Change-Id: I933c232d2fbecc6b9bdc364ebac181798bce9175
Tested-by: Daniel Andersson <daniel.r.andersson@volvocars.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334270
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-08 21:06:37 +00:00
a2ff20dd20 manifest_xml: Add Load and Unload methods
- do not call the internal method from subcmds/sync.py.
- use the correct default groups for submanifests.
- only sync the superproject when we are told to.

Change-Id: I81e4025058f1ee564732b9e17aecc522f6b5f626
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334639
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-04-08 19:52:04 +00:00
55ee304304 Fix sub manifest handling
Also fixes some typos

Change-Id: Id2ba5834ba3a74ed3f29c36d2c0030737dc63e35
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334579
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-06 21:04:46 +00:00
409407a731 init: add multi-manifest support
This moves more of the manifest project handling into ManifestProject.

Change-Id: Iecdafbec18cccdfd8e625753c3bd1bcddf2b227f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334520
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-06 17:02:40 +00:00
d82be3e672 Move manifest config logic into ManifestProject
Use ManifestProject properties for config values.

Change-Id: Ib4ad90b0d9a089916e35615b8058942e6d01dc04
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334519
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-06 16:59:45 +00:00
9b03f15e8e project: add ManifestProject.Sync()
Move the logic to sync a ManifestProject out of subcmds/init.py

Change-Id: Ia9d00f3da1dc3c5dada84c4d19cf9802c2346cb0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334140
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-04-01 15:48:04 +00:00
9b72cf2ba5 project: Isolate ManifestProject from RepoProject
Create RepoProject and ManifestProject, inheriting from MetaProject,
  with methods separated for isolation and clarity.

Change-Id: Ic1d6efc65c99470290fea612e2abaf8670d199f4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334139
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-03-31 21:02:52 +00:00
5d3291d818 manifest_file must be an absolute path
Correctly pass the full path of the manifest file for the submanifest.
The manifest-name in the <submanifest/> element was being passed in
as given, which caused it to not be found since the current directory
never set. (b/226333721: fails when manifest-name is given.)

Also verify that the manifest_file passed to XmlManifest() is an
absolute path.

Bug: https://b.corp.google.com/issues/226333721
Change-Id: I23461078233e34562bc2eafeb732cfe8bd38ddc1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/333861
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-03-23 21:18:41 +00:00
244c9a71a6 trace: allow writing traces to a socket
Git can write trace2 events to a Unix domain socket [1]. This can be
specified via Git's `trace2.eventTarget` config option, which we read to
determine where to log our own trace2 events. Currently, if the Git
config specifies a socket as the trace2 target, we fail to log any
traces.

Fix this by adding support for writing to a Unix domain socket,
following the same specification that Git supports.

[1]: https://git-scm.com/docs/api-trace2#_enabling_a_target

Change-Id: I928bc22ba04fba603a9132eb055141845fa48ab2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/332339
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Josh Steadmon <steadmon@google.com>
2022-03-16 17:33:07 +00:00
b308db1e2a manifest_xml: group for submanifest projects
Add all projects in a submanifest to the group
submanifest::<path_prefix> for ease in filtering.

Change-Id: Ia6f01f9445f4f8d20fda3402f4d5821c43ceaf7f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/331319
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-02-28 20:08:58 +00:00
cc879a97c3 Add multi-manifest support with <submanifest> element
To be addressed in another change:
 - a partial `repo sync` (with a list of projects/paths to sync)
   requires `--this-tree-only`.

Change-Id: I6c7400bf001540e9d7694fa70934f8f204cb5f57
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322657
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-02-17 21:57:55 +00:00
87cce68b28 Move local-manifest check to manifest_xml.py
This removes the need for git_superproject to include manifest_xml, and
puts the logic for local_manifest detection in one place.

Change-Id: I4d33ded0542ceea4606a1ea24304f678de20c59e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/330499
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2022-02-15 22:15:42 +00:00
adaa1d8734 project.py: pass --recurse-submodules={value}
If submodules is False, explicitly pass '=no'.  Uninitialized submodules
may cause the default option to fail.

Change-Id: Ia00bcba5b69c4b65195f4c469c686a3ef9a4a3ad
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/330159
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-02-10 23:57:45 +00:00
8e91248655 project: mark gc.log as safe to discard when migrating .git/
This is just a log file that, while useful for humans when gc aborts,
doesn't contain any data, so it's safe to throw away.

Bug: https://crbug.com/gerrit/15619
Change-Id: Ia95e0e281f52260668f7a80b5d5f990e32a8597a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328999
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-01-26 16:46:03 +00:00
630876f9e4 init: add an option --enable-git-lfs-filter
It was reported that git-lfs did not work with git-repo. Specifically,
`git read-tree -u` run by `repo sync` would fail git-lfs's smudge
filter. See https://github.com/github/git-lfs/issues/1422.

In fact, by the time `git read-tree -u` is run, the repository is not
bare. It is just that, the working directory is not the same as the
.git directory. git-lfs's filter should work. No one seems to have
delved into that issue.

Today, with newer versions of git-repo and git-lfs, that issue will
not reproduce. Tested with
- git 2.33, git-lfs 2.13 on macOS
- git 2.17, git-lfs 2.3 on ubuntu

So, it seems fine to add an option --enable-git-lfs-filter, default to
false, and stat that it may not work with older versions of git and
git-lfs in the help doc.

Bug: https://crbug.com/gerrit/14516
Change-Id: I8d21854eeeea541e072f63d6b10ad1253b1a9826
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328359
Tested-by: XD Trol <milestonejxd@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-01-26 01:47:20 +00:00
4aa8584ec6 init: make bad --repo-rev settings more clear
If the user passes a bad --repo-rev setting in a new checkout, add a
tip to the error message that their option is probably bad instead of
just saying "unable to resolve".

If the user has already initialized a checkout, we'd display a raw
traceback which would confuse them.  Swallow that and also include
the --repo-rev tip.

Bug: https://crbug.com/gerrit/15610
Change-Id: I5d72513c7b37bf9bb5d19862fcdfaf0d1f44e886
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328820
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-25 18:54:42 +00:00
b550501254 project: Ignore failure to remove the sample hooks
Removing the sample hooks is just clean up, so if repo cannot remove a
sample hook that should not cause it to fail.

Change-Id: I716b977da091c22b8f53e134f4fbc114116f9a65
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328635
Reviewed-by: Mike Frysinger <vapier@google.com>
2022-01-22 06:13:10 +00:00
a535ae4418 branches: Fix "not in" handling
If the branch is current, or present in less than half of the projects,
list which projects it is *in*.

Otherwise, correctly detect which projects (by relpath) it is not in.

Previously, the "not in" path would incorrectly list all projects.

Change-Id: Ia153856f577035a51f538b7bf5d3135b70c69d52
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328199
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-01-21 17:20:21 +00:00
67d6cdf2bc project: store objects in project-objects directly
In order to stop sharing objects/ directly between shared projects,
we have to fetch the remote objects into project-objects/ manually.
So instead of running git operations in the individual project dirs
and relying on .git/objects being symlinked to project-objects/,
tell git to store any objects it fetches in project-objects/.

We do this by leveraging the GIT_OBJECT_DIRECTORY override.  This
has been in git forever, or at least since v1.7.2 which is what we
already hard require.  This tells git to save new objects to the
specified path no matter where it's being run otherwise.

We still otherwise run git in the project-specific dir so that it
can find the right set of refs that it wants to compare against,
including local refs.  For that reason, we also have to leverage
GIT_ALTERNATE_OBJECT_DIRECTORIES to tell git where to find objects
that are not in the upstream remote.  This way git doesn't blow up
when it can't find objects only associated with local commits.

As it stands right now, the practical result is the same: since we
symlink the project objects/ dir to the project-objects/ tree, the
default objects dir, the one we set $GIT_OBJECT_DIRECTORY to, and
the one we set $GIT_ALTERNATE_OBJECT_DIRECTORIES to are actually
all the same.  So this commit by itself should be safe.  But in a
follow up commit, we can replace the symlink with a separate dir
and git will keep working.

Bug: https://crbug.com/gerrit/15553
Change-Id: Ie4e654aec3e1ee307eee925a54908a2db6a5869f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328100
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-19 17:24:51 +00:00
152032cca2 project: move --reference handling to project-objects
When using --reference, the path is written to objects/info/alternates.
The path is accessed inconsistently -- sometimes through projects/ (via
self.gitdir) and sometimes through project-objects/ (via self.objdir).
This works because projects/.../objects is a symlink to the objects dir
under project-objects/.  Change all accesses to go through self.objdir.
This will allow us to stop symlinking projects/.../objects without the
reference dir logic breaking.  The projects/ path is going to use its
alternates file for its own needs.

Bug: https://crbug.com/gerrit/15553
Change-Id: I6b452ad1aaffec74ecb7ac1bb9baa3a3a52e076c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/328099
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jack Neus <jackneus@google.com>
2022-01-13 18:27:28 +00:00
a3ac816278 test_project: use os.readlink instead of Path.readlink
Path.readlink is only available on Python 3.9, breaking compatibility
with all python versions below. os.readlink is already used in other
places of this file, so use it here as well.

Change-Id: I5acf8f5334a3e7c8de9cea1939d7e2b9af5f30ae
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327844
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Sebastian Wagner <sebix@sebix.at>
2022-01-11 20:16:42 +00:00
98bb76577d project: prune sample hooks
These hooks are never used and often get stale, so just trim them.
Users rarely look in these dirs to begin with.

Change-Id: Ic785aa55fb7ec84a61376df101127d0018882030
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327538
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-10 17:41:45 +00:00
d33dce0b77 project: drop support for symlinking internal .git files
Since we don't do this anymore, and there prob won't be a need to
bring it back, drop support for it.

Bug: https://crbug.com/gerrit/15460
Change-Id: I7d86706f108c797a5c7962cb1578693d49430367
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327537
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-10 17:41:40 +00:00
89ed8acdbe project: abort a bit earlier before migrating .git/
Verify all the .git/ paths will be handled by the migration logic before
starting the migration.  This way we still abort & log an error, but the
user gets to see it before we put the tree into a state that they have to
manually recover.  Also add a few more known-safe-to-clobber paths.

Bug: https://crbug.com/gerrit/15273
Change-Id: If49d69b341bc960ddcafa30da333fb5ec7145b51
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327557
Reviewed-by: Colin Cross <ccross@android.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-07 20:17:14 +00:00
71e48b7672 Revert "sync: dropped "NOTICE: --use-superproject is in beta ..." message."
This reverts commit d53cb9549a. As long as
repo's reference docs treat this feature as a work in progress and don't
cover it well enough to allow all repo maintainers to easily support it,
it is inconsistent to report to users that it is no longer in beta.
Thanks for vapier@google.com for noticing.

https://crbug.com/gerrit/15527 tracks the required documentation changes
before we'd be ready to roll forward again.

Change-Id: Ic9bd951cfb3c1abf6e1bfa30dfe4afa1c9b7bec6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327337
Reviewed-by: Jonathan Nieder <jrn@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Jonathan Nieder <jrn@google.com>
2022-01-06 20:01:03 +00:00
13576a8caf project: stop symlinking info dir under .git/
Unsharing this directory shouldn't be a problem.  The current repo code
treated it as a file, and while that's actually incorrect, files & dirs
are basically treated the same, so it's practically the same.

Let's enumerate each subpath since there aren't that many.

info/refs:
Only used when the project is exported over git dumb transports (i.e.
a http:// server).  Repo never does this, and it's extremely unlikely
any user has ever done this.  Plus, this proposal talks about unsharing
project refs, so this file should get unshared too.

info/grafts:
A user-configurable file that repo never touches.  Might be useful to
share across projects, but probably rarely (if ever) used by developers,
and forcing them to configure it for each project isn't that big of a
deal.

info/exclude:
info/attributes:
User-configurable files that repo never touches.  Doesn't seem like
most users ever touch these, and if they do, having them do it for
each shared project isn't a big deal.

info/sparse-checkout:
Repo doesn't use sparse checkouts, and it's extremely unlikely to even
work if a user tried doing something themselves.

Bug: https://crbug.com/gerrit/15460
Change-Id: I53e44d73a6d7a92da615b46600d8ea51cb46e3ac
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327519
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-06 08:31:45 +00:00
2345906d04 project: stop symlinking description file under .git/
Nothing uses this path.  It’s only for exporting git dirs e.g. for
online gitweb use which probably no one does.  It is not the same
description file as exists on servers we cloned from.  Leaving it
as the default plain text file will simplify code.

We don't undo any existing symlinks if they exist since repo does
not care about them, and their existence doesn't hurt.

Bug: https://crbug.com/gerrit/15460
Change-Id: Ic34fe7c3cfb8f6da844de5be30158f59382b1cc8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327518
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-06 08:29:06 +00:00
41289c62b4 project: stop symlinking svn under .git/
This path only matters to users of `git svn` who manually run it in
local projects after they get a full repo client checkout.  With svn
usage falling in general, and with the fact that the source checkout
now symlinks its .git/ state to the internal projects/ path, we don't
need to manage this anymore.

It means the path won't be shared among multiple local projects that
have the same remote, but so it goes.  It was an optimization only,
not functionality required for correctness.  We want to simplify the
internals to stop messing with git state, and this particular path
doesn't seem worth the effort to maintain.

We don't undo any existing svn symlinks if they exist since repo does
not care about them, and their existence doesn't hurt anything.

Bug: https://crbug.com/gerrit/15460
Change-Id: Ie8496b275bcc589771aa9f4ee874ed2ee6d5241d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327517
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2022-01-06 08:28:37 +00:00
c72bd8486a project: clean up now unused code
Now that we symlink worktree .git/ paths to .repo/projects/, we never
set share_refs=True anywhere, which means all of this logic is dead
code.  Throw it all away.  Do it as a separate commit to make the
parent commit easier to review.

Bug: https://crbug.com/gerrit/15273
Change-Id: If496d39029d3d3bd523ba24c603ce47a63ad9b51
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/326817
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jack Neus <jackneus@google.com>
2022-01-06 04:08:05 +00:00
d53cb9549a sync: dropped "NOTICE: --use-superproject is in beta ..." message.
Tested the code with the following commands.

$ ./run_tests -v

Bug: [google internal] b/209511230
Change-Id: Ia3c6de47709f5276e324a5bb608383aba3b2c562
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327197
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-12-29 19:07:08 +00:00
cf0ba48649 sync: With --mirror option, don't display no-use-superproject... message.
+ Display 'Defaulting to no-use-superproject because there is no working tree.'
  message if --use-superproject option is used and we are not using
  superproject because manifest is either a mirror or is an archive.

Tested the code with the following commands.

$ ./run_tests -v

Tested the sync code by using repo_dev alias and pointing to this CL.

$ repo init -u https://android.googlesource.com/mirror/manifest --mirror

$ repo_dev sync
Receiving objects: 100% (3/3), done.eiving objects:  33% (1/3)

$ repo_dev sync --use-superproject
Defaulting to no-use-superproject because there is no working tree.
Fetching:  0% (0/2158) warming up

Bug: https://crbug.com/gerrit/15368
Change-Id: I16b87ee9623315dbc3100b612b1decdaab7ac1dc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/325797
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-12-07 16:46:41 +00:00
2a089cfee4 project: migrate worktree .git/ dirs to symlinks
Historically we created a .git/ subdir in each source checkout and
symlinked individual files to the .repo/projects/ paths.  This layer
of indirection isn't actually needed: the .repo/projects/ paths are
guaranteed to only ever have a 1-to-1 mapping with the actual git
checkout.  So we don't need to worry about having files in .git/ be
isolated.

To that end, change how we manage the actual project checkouts from
a dir full of symlinks (and a few files) to a symlink to the internal
.repo/projects/ dir.  This makes the code simpler & faster.

The directory structure we have today is:
.repo/
  project-objects/chromiumos/third_party/kernel.git/
    <paths omitted as not relevant to this change>
  projects/src/third_party/kernel/
    v3.8.git/
      config
      description   -> …/project-objects/…/config
      FETCH_HEAD
      HEAD
      hooks/        -> …/project-objects/…/hooks/
      info/         -> …/project-objects/…/info/
      logs/
      objects/      -> …/project-objects/…/objects/
      packed-refs
      refs/
      rr-cache/     -> …/project-objects/…/rr-cache/
src/third_party/kernel/
  v3.8/
    .git/
      config        -> …/projects/…/v3.8.git/config
      description   -> …/project-objects/…/v3.8.git/description
      HEAD
      hooks/        -> …/project-objects/…/v3.8.git/hooks/
      index
      info/         -> …/project-objects/…/v3.8.git/info/
      logs/         -> …/projects/…/v3.8.git/logs/
      objects/      -> …/project-objects/…/v3.8.git/objects/
      packed-refs   -> …/projects/…/v3.8.git/packed-refs
      refs/         -> …/projects/…/v3.8.git/refs/
      rr-cache/     -> …/project-objects/…/v3.8.git/rr-cache/

The directory structure we have after this commit:
.repo/
  <nothing changes>
src/third_party/kernel/
  v3.8/
    .git            -> …/projects/…/v3.8.git

Bug: https://crbug.com/gerrit/15273
Change-Id: I9dd8def23fbfb2f4cb209a93f8b1b2b24002a444
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323695
Reviewed-by: Mike Nichols <mikenichols@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-12-01 15:27:16 +00:00
4a478edb44 init, sync: fixed flake8 warnings.
Tested:
+ run_tests
+ flake8 subcmds/init.py
+ flake8 subcmds/sync.py

Change-Id: Ie337481d8a210bfc49b0745f75c05a308a0e74d3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/324155
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-11-18 16:22:40 +00:00
6bd89aa657 superproject: Inherit --no-use-superproject with --mirror option.
init.py
+ Similar to opt.archive, gave an error if --mirror option is
  used with --use-superproject.

sync.py
+ Defaulted to --no-use-superproject if manifest is a mirror or
  archive (similar to error at line# 1067).

Tested:
+ run_tests
+ flake8 (will fix known errors in another CL).

$ repo_dev init -u sso://googleplex-android.git.corp.google.com/platform/manifest --use-superproject --mirror
Usage: repo init [options] [manifest url]

main.py: error: --mirror and --use-superproject cannot be used together.

+ repo init and repo sync with --mirror and without --mirror
  options.
  $ repo_dev init -u https://android.googlesource.com/platform/manifest
  $ repo_dev sync
    ...superproject.git: Initial setup for superproject completed.

+ With --mirror option, verfied there are no exceptions in git_superproject.py

Bug: [google internal] b/206537893
Change-Id: I059f20e76f0ab36f0587f29779bb53ede4663bd4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323955
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-11-18 01:27:41 +00:00
9c1fc5bc5d sync: Handle tag ref in "upstream" field
repo sync only handles a git tag properly when it is in the "revision"
field. However, "revision locked manifests" (`repo manifest
--revision-as-HEAD`) specifies the tag in the "upstream" field. The
issue is that this tag is not fetched. Only the commit that the tag
points to is fetched. This cases issues as
self._CheckForImmutableRevision() runs and comes to the conclusion that
the tag was changed while in fact, it was just not fetched. This causes
a full sync.

File docs/manifest-format.md, section Element-project:
> Attribute upstream: Name of the Git ref in which a sha1 can be found.
Used when syncing a revision locked manifest in -c mode to avoid having
to sync the entire ref space. Project elements not setting their own
upstream will inherit this value.

Change-Id: I0507d3a5f30aee8920a9f820bafedb48dd5db554
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323620
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Robin Schneider <ypid@riseup.net>
2021-11-16 20:43:57 +00:00
333c0a499b project: init hooks in objdir only
objdir is the .repo/project-objects/ dir based on the remote path.
gitdir is the .repo/projects/ dir based on the local source checkout
path.  When we setup the gitdir, we symlink "hooks" to the one in the
objdir.  But when we go to initialize the hooks, we do it via gitdir.
There is a 1-to-many mapping from project-objects to projects, so
initializing via gitdir can be repetitive.  Collapse the hook init
logic to the objdir init path.

Change-Id: I828fca60ce6e125d6706c709cdb2797faa40aa50
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323815
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-11-15 19:50:18 +00:00
fdeb20f43f sync: link the internal-fs-layout doc into checkouts
This should make it easy to discover for people poking around .repo/.

Change-Id: Ie5051551f25127c0592df5e36efba7bb2263e5d4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323701
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-11-15 01:39:53 +00:00
bf40957b38 git-review: add config file
This is used by the `git review` tool that some people use.

Change-Id: I8dac4e1dad155109a05181deaec61e1a74857b1f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323698
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-11-15 01:39:36 +00:00
f9e81c922d SUBMITTING_PATCHES: link to commit message style docs
Change-Id: I2090ebc43fc1c816b941a53dd89dbedf7bc61289
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323696
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jack Neus <jackneus@google.com>
2021-11-15 01:39:16 +00:00
e6601067ed man: refresh pages
Change-Id: I3f2c3ad77c16a76276bba2954887ab9e7605661c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323516
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-11-12 17:30:45 +00:00
3001d6a426 help: fix grammar in help text
Bug: https://crbug.com/gerrit/14838
Change-Id: Ic5000921ba9a1baa086153630ebbb429e3d17642
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323515
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-11-12 17:30:32 +00:00
00c5ea3787 Fix typo for ValueError
which will cause error log like below:
NameError: name 'ValueErrorl' is not defined

Change-Id: I388886b7cf6d700e224c3847b7ba4ba4fe9c041d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323015
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: 彭杨益 <pyy101727@gmail.com>
2021-11-07 02:32:08 +00:00
0531a623e1 sync: make --prune the default
If a remote deletes a ref, and it points to an object that doesn't
exist locally, we can get into a bad state, and the only way for the
user to recover is to run `repo sync --prune` (and to know that is
the option they need).  The error message is not helpful:

fatal: bad object refs/remotes/cros/firmware-zork-13421.B-master
error: https://chromium.googlesource.com/chromiumos/platform/ec did not send all necessary objects

This situation can also come up when the remote renames refs in a
UNIX FS incompatible way.  For example, replacing refs/heads/foo
with refs/heads/foo/bar.

Also add a --no-prune option for users to disable the behavior.

Bug: https://issuetracker.google.com/203366450
Change-Id: Icf45d838a10938feb091d29800f7e49240830ec3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322956
Reviewed-by: Andrew Lamb <andrewlamb@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-11-05 20:13:30 +00:00
2273f46cb3 sync: fix --tags option
This has been broken since it was added where --tags was actually
the same as --no-tags.  Oddly, it was copied from init where the
logic is correct.

Bug: https://crbug.com/gerrit/12401
Change-Id: I15b89da1a655176a11bebc22573b25c728055328
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322955
Reviewed-by: Andrew Lamb <andrewlamb@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-11-05 20:13:02 +00:00
7b9b251a5e project: fix format string in error message
BUG=None

Change-Id: I0b195fd919c6db8cb3547e8d6f4c733f2bd4a535
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322735
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2021-11-05 16:59:14 +00:00
6251729cb4 superproject: added 'implies -c' in the help of --use-superproject option.
sync.py: deleted unused import errno.

Tested:
$ ./run_tests
$ flake8 repo subcmds/sync.py

Bug: https://crbug.com/gerrit/15208
Change-Id: I2bb3098f5602ded3861e000100766041ad93b53d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322555
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-11-01 22:15:53 +00:00
11b30b91df Support more url schemes for getting standalone manifest
urllib.requests.urlopen also supports file, so call it unless the
scheme is 'gs'.  This adds http, https, and ftp support.

Change-Id: I3f215c3ebd8e6dee29ba14c7e79ed99d37287109
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322095
Reviewed-by: Michael Kelly <mkelly@arista.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Matt Story <mstory@arista.com>
2021-10-27 13:20:35 +00:00
198838599c fetch: Fix stderr handling for gsutil
Previously gsutil stderr was getting piped into stdout, which
yields bad results if there are non-fatal warnings in stderr.

Additionally, we should fail outright if gsutil fails (by adding
`check = True`) rather than fail later on when we try to sync to
a manifest that is in fact just a stderr dump.

BUG=none
TEST=manual runs with bad gs urls

Change-Id: Id71791d0c3f180bd0601ef2c783a8e8e4afa8f59
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/321935
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-10-26 22:18:28 +00:00
282d0cae89 ssh: handle FileNotFoundError errors
If ssh isn't installed, it throws a distinct error we have to catch.

Bug: https://crbug.com/gerrit/15196
Change-Id: I0660e842c304ce7575f5cb100894d05fd65f9454
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/322055
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-10-26 16:18:45 +00:00
03ff276cd7 sync: properly handle standalone manifests for sync command
sync should not attempt to sync the manifest project if it was
created from a standalone manifest. The current work around is to
run sync with --nmu.

BUG=none
TEST=manual runs

Change-Id: I2e121af0badf9642143e77c7af89d1c2d993b0f3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/321195
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-10-15 17:20:00 +00:00
4ee4a45d03 subcmds/sync: Use pack-refs instead of gc for redundant gitdirs.
Previously `git gc` was being run on every gitdir even when they shared
the same objects. Instead only call it once and use pack-refs for the
gitdirs that were not gc'ed.

Bug: https://crbug.com/gerrit/15113
Test: repo sync -j # and check that git pack-refs is called
Change-Id: Icff37ab3ec78cfb44391d8cc7f2d875991532320
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/320275
Tested-by: Allen Webb <allenwebb@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-10-14 12:27:12 +00:00
0f6f16ed17 repo: more arg checking for --standalone-manifest re-inits
`repo init` doesn't do anything on re-init when the checkout has
been initialized using --standalone manifest. Rather than let the
tool run through its existing flows (which happen to noop), check
the args and explicitly quit if a bare `repo init` is run on a
standalone checkout.

BUG=none
TEST=manual tests

Change-Id: Ie4346ef6df1282ec3e3f8045a08138c93653fece
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/320735
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-10-11 18:58:11 +00:00
76491590b8 repo: fix bug with --standalone-manifest
We were accidentally always setting manifest.standlone in config,
which was messing up behavior for standard use cases.

BUG=gerrit:15160
TEST=manual runs

Change-Id: Ic80f084ae97de5721aced3bb52d3ea9115f8d833
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/320715
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-10-11 18:57:57 +00:00
6a74c91f50 sign-launcher: make the help text more automatic
Rather than display "3.0" all the time and confuse people, extract
the version from the launcher we're signing and display that.

Also reformat the text to follow our current practice: upload the
versioned launcher by itself first, and then later copy that over
the default.

And while we're here, add tips for rollbacks.

Change-Id: I1654425c88e5c67d78879f2f33ad685c59be14dc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319637
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-10-06 17:02:56 +00:00
669efd0fd7 subcmds/sync: Disable autoDetach for git gc.
gc.autoDetach is enabled by default which makes 'git gc --auto' return
immediately and run in background. This can lead to a pile up of
operations all using large amounts of memory at the same time. To avoid
this set gc.autoDetach to false so that the garbage collect task waits
for instances to finish before spawning more.

Bug: https://crbug.com/gerrit/15113
Test: repo sync -j # and check the number of 'git gc' processes
Change-Id: Ic0815156ba3db03972968f33f6f9f51e4928f23b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319835
Tested-by: Allen Webb <allenwebb@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-10-05 14:12:01 +00:00
a0f6006ae7 git_config: Fixed test.gitconfig getting updated when running tests.
Moved test_GetSyncAnalysisStateData to GitConfigReadWriteTests class.

Deleted [repo "syncstate*..] data from tests/fixtures/test.gitconfig.

Tested:
./run_tests
...
tests/test_git_config.py::GitConfigReadWriteTests::test_GetSyncAnalysisStateData PASSED [ 84%]
...

Bug: https://crbug.com/gerrit/15103
Change-Id: I8cb89ce10b025994a045106c9c66dd243ae8ba50
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319557
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-30 21:45:09 +00:00
2ddbf8a8bf Merge "Merge history of v2.14.5." into main 2021-09-30 21:37:09 +00:00
445723fd37 Merge history of v2.14.5.
The v2.14.[345] releases were cut on a branch based on v2.14.2.  We
had some regression fixes we wanted in v2.14, but too many risky
changes landed in main since to cut another v2.14.x directly, and
we didn't want to destabilize even more by pushing a v2.15 right
away.  So we branched to keep things healthy.

But people with old checkouts trying to upgrade from those versions
run into an old repo bug where it only selfupdates with fast-forwards,
and repo can't fast-forward from those divergent histories.  So let's
do a merge commit to stitch the history back together.

There's no actual changes in here.

Change-Id: I05a96048e3846321e57c5f5224fb8dcf3c191d35
2021-09-30 21:36:56 +00:00
436bde5137 Merge history of v1.13.11.
For older versions of repo, this would make it easier for it to perform
a self update by making it a fast-forward from the following tags:

  v1.13.9.2, v1.13.9.3, v1.13.9.4, v1.13.10, v1.13.11

Change-Id: Ia75776312eaf802a150db8bd7c0a6dce57914580
2021-09-30 11:49:08 -07:00
4f88206178 trace2_event: Add remove_prefix to fix failing tests on Linux & macOS.
removeprefix is available i python 3.9. Mac and Linux are running in
a version below 3.9. Thus tests are failing with the following error:
  "AttributeError: 'str' object has no attribute 'removeprefix' "

Replaced the removeprefix with custom function which we will delete
once Linux and macOS versions are updated.

Tested:
$ ./run_tests

Bug: [google internal] b/201453085
Change-Id: I9b4d564ff1176e1b4471805ef05472c1914cd9f9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319375
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-29 21:24:59 +00:00
f88282ccc2 git_config: update error handling with no config file
Now that _do throws an exception when `git` fails, update the logic
that tries to read config files but the file doesn't exist.

Bug: b/192664812
Change-Id: I6417ecd70891b8f2d5f2bdb819f91df69ac4b70c
Test: `repo upload` no longer crashes when .repo/config doesn't exist
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319295
Reviewed-by: Jack Neus <jackneus@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-09-29 01:02:47 +00:00
8967a5aec6 launcher: bump version for new release
Change-Id: I9812185c9dfc11289547f5956c0cbe567d720f7f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319335
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-09-28 20:54:56 +00:00
2f3c3316e4 Update revisionId if required when using extend-project
When a hard revision ID is provided in a regular project tag then the
revisionId is updated as well if it is a commit hash.  The difference
is that if the revisionExpr is a commit, git-repo needs to update
refs/remotes/m/master with update-ref not symbolic-ref, as the latter
must refer to another ref, not to a specific commit.

Change-Id: I215a62dabb30225e480ad2c731416d775fc0c750
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/310963
Tested-by: Michael Kelly <mkelly@arista.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-28 20:12:00 +00:00
37c21c268b Add 'dest-path' to extend-project to support changing path
This allows us to move the repository to a new location in the source
tree without having to remove-project + add a new project tag.

Change-Id: I4dba6151842e57f6f2b8fe60cda260ecea68b7b4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/310962
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Michael Kelly <mkelly@arista.com>
2021-09-28 20:12:00 +00:00
b12c369e0b superproject: Only trigger enrollment logic when manifest have it.
The current code would check for enrollment status when the user did not
explicitly specify --[no-]use-superproject and do not have a remembered
value in their repo client. However, because superproject only makes
sense for manifests that have one specified, we should skip the
enrollment logic in that case.

Address this by checking manifest.superproject prior to proceeding. This
would avoid showing the greeting message of superproject enrollment
which can be confusing for developers.

Tested:
  For manifest without superproject:
   - repo sync --use-superproject will still show message for
     superproject;
   - repo sync will not show message regardless of enrollment state
  For manifest with superproject:
   - repo sync will show message and perform enrollment if not
     previously enrolled

Bug: https://crbug.com/gerrit/15039
Change-Id: Ic2be9f9d037f0e7cf3446da474a5a0d0e4bd88da
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319255
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-28 19:42:01 +00:00
bbe8836494 superproject: Log syncstate's parameter as data-json it it is an array.
All the values of syncstate are strings, check the first byte and last
byte to see if it is an array. For syncstate data, there were no false
positives.

Tested:
$ repo_dev sync

Verified event logged for argv is "data-json".

$./run_tests

Bug: [google internal] b/201102002
Change-Id: Id56adb532b80267f08d09147ac663cdd5987ce87
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319075
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-09-28 18:22:49 +00:00
9d96f58f5f make file removal a bit more robust
Some of the file removal calls are subject to race conditions (if
something else deletes the file), so extend our remove API to have
an option to ignore ENOENT errors.  Then update a bunch of random
call sites to use this new functionality.

Change-Id: I31a9090e135452033135337a202a4fc2dbf8b63c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319195
Reviewed-by: Sean McAllister <smcallis@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-09-28 16:06:50 +00:00
7a1e7e772f repo: add support for reading standalone manifests from disk
BUG=b:192664812
TEST=existing tests (no coverage), manual runs

Change-Id: Ic032417ecfca77d5e0de1b1ff62b30ce8205bfc5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/318715
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-28 16:03:21 +00:00
c474c9cba1 repo: Add support for standalone manifests
Added --standalone_manifest to repo tool. If set, the
manifest is downloaded directly from the appropriate source
(currently, we only support GS) and used instead of creating
a manifest git checkout. The manifests.git repo is still created to
keep track of various config but is marked as being for a standalone
manifest so that the repo tool doesn't try to run networked git
commands in it.

BUG=b:192664812
TEST=existing tests (no coverage), manual runs

Change-Id: I84378cbc7f8e515eabeccdde9665efc8cd2a9d21
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312942
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-28 15:40:46 +00:00
956f7363d1 superproject: Log argv parameter of syncstate as 'data-json'.
Fixed: "we need to make a special case for logging the argv; it
should probably be a "data-json" event so that we log this directly as
an array rather than an encoded string.

Tested:
$ repo_dev sync

Verified event logged for argv is "data-json".

$./run_tests

Bug: [google internal] b/201102002
Change-Id: I18ccec79c73c8dc931cb8afc472b2361db8aea4c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319055
Reviewed-by: Josh Steadmon <steadmon@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-09-27 19:11:14 +00:00
6f8c1bf4ff Fix indent error which would have prevented choice expiration to work.
Change-Id: I077e05eea23ad58d1dde2c9fe5608660a56d03e5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/318815
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Amith Dsouza <amithds@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2021-09-27 18:59:21 +00:00
e0b16a22a0 superproject: support a new revision attribute.
Tested:
$ ./run_tests

Verified that a manifest that specified superproject revision would use
the specified revision, and superproject will use the default revision.

Note that this is a slight behavior change from earlier repo versions,
which would always use the branch name of the manifest itself. However,
the new behavior would be more consisitent with regular "project"
element and would allow superproject be used even if it is not enabled
for the particular manifest branch, so we have decided to make the
change as it would provide more flexibility and better matches what
other elements would do.

Bug: [google internal] b/187868160
Change-Id: I35255ee347aff6e65179f7879d52931f168b477e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/317643
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-27 06:36:05 +00:00
d669d2dee5 release-process: update distro baseline & add OpenSSH
Stop tracking Ubuntu Trusty & Xenial and Debian Jessie & Stretch
as they only had Python 3.5 available which we've dropped.

Backfill OpenSSH versions since we've started testing for it.

Change-Id: I03183ed97f6e43dce8a00e36cce2956544a26afc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/318835
Reviewed-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-09-24 16:42:56 +00:00
366824937c platform_utils: os.rename exception when src and des on different file system
Symptom: repo sync exception
Root Cause: os.rename only works when source and destination are on the same file system
Solution: using shutil.move

to save disk usage, I create links for projects and project-objects, link to folder on another disk
lrwxrwxrwx  1 owenwen owenwen   47 Jun  9 16:40 project-objects -> /disk3/AndroidLocalRepos/.repo/project-objects/
lrwxrwxrwx  1 owenwen owenwen   40 Jun  9 16:40 projects -> /disk3/AndroidLocalRepos/.repo/projects/

below are exception I met:
"""
Traceback (most recent call last):
  File "/usr/lib/python3.6/multiprocessing/pool.py", line 119, in worker
    result = (True, func(*args, **kwds))
  File "/usr/lib/python3.6/multiprocessing/pool.py", line 44, in mapstar
    return list(map(*args))
  File "/disk2/Android11/.repo/repo/subcmds/sync.py", line 550, in _CheckoutOne
    project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
  File "/disk2/Android11/.repo/repo/project.py", line 1251, in Sync_LocalHalf
    self._InitWorkTree(force_sync=force_sync, submodules=submodules)
  File "/disk2/Android11/.repo/repo/project.py", line 2801, in _InitWorkTree
    self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
  File "/disk2/Android11/.repo/repo/project.py", line 2674, in _CheckDirReference
    platform_utils.rename(dst_path, src_path)
  File "/disk2/Android11/.repo/repo/platform_utils.py", line 127, in rename
    os.rename(src, dst)
OSError: [Errno 18] Invalid cross-device link: '/disk2/Android11/system/libhidl/.git/packed-refs' -> '/disk2/Android11/.repo/projects/system/libhidl.git/packed-refs'
"""

Change-Id: Ifda2f16530cc5a8f280169f482ee858f9e5241d3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/316002
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-09-24 08:20:06 +00:00
a84f43a006 manifest: make repo-hooks more robust wrt element ordering
Currently, repo will fail to sync to a manifest if the definition
of the repo-hooks project comes after the repo-hooks element.

BUG=none
TEST=new test, run_tests

Change-Id: I0bf85625173492af6c6404d4b67543e96e670562
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/318520
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Jack Neus <jackneus@google.com>
2021-09-23 21:17:38 +00:00
0468feac39 update-manpages: avoid regen just for datestamp update
To avoid noise due to the passage of time, don't regenerate man pages
if the only thing different is the datestamp in the header.

Change-Id: Ic8d7b08d12e59c66994c0cc2d4ec2d2ed3eb6e6d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/318575
Reviewed-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-09-22 19:37:35 +00:00
0ec2029833 superproject: Move enrollment to opt-out when enabled globally
Our internal experiments was a success so far and we are enrolling 100%
users now.  Instead of asking every two weeks, simply consider a lack of
unexpired choice as accepting the system default.

With this change the user would still be able to override the system
default with --no-use-superproject, or to permanently set the choice in
user's profile with git config --global repo.superprojectchoice.

Bug: [google internal] b/190688390
Change-Id: Idc77a9cbf88a169d90304169e91f0d722dc4ac8b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/317975
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2021-09-20 07:21:22 +00:00
d8e8ae8990 superproject: Log branch and remote url with every log message.
Saved superproject's remote URL in _remote_url data and used it
in the _Fecth function.

Tested:
$ ./run_tests
$ flake8 git_superproject.py
$ repo_dev init --use-superproject -u https://android.googlesource.com/platform/manifest
$ repo_dev sync

   Verified the all log messages have the following format.
   repo superproject branch: <branch> url: <url> warning: <message>

Bug: [google internal] b/200072098
Change-Id: Iac6af7c99225479fd50bc6909396b22e0ce5f76b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/318177
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-09-16 15:46:19 +00:00
6448a4f2af sync: Log repo sync state events as 'data' events.
git_trace2_event_log.py:
+ Added LogDataConfigEvents method to log 'data' events.
  Sync's current_sync_state and previous_sync_state are logged
  as 'data' events in the current log.

  It logs are key/value in the |config| argument. Each key is
  prefixed with |prefix| argument.

  The following are sample events that are logged during repo sync.

   {"event":"data",
   "sid":"repo-20210914T181545Z-P000330c0/repo-20210914T181545Z-P000330c0",
   "thread":"MainThread",
   "time":"2021-09-14T18:16:19.935846Z",
   "key":"previous_sync_state/repo.syncstate.main.synctime",
   "value":"2021-09-14T17:27:11.573717Z"}

   {"event":"data",
   "sid":"repo-20210914T181545Z-P000330c0/repo-20210914T181545Z-P000330c0",
   "thread":"MainThread",
   "time":"2021-09-14T18:16:19.955546Z",
   "key":"current_sync_state/repo.syncstate.main.synctime",
   "value":"2021-09-14T18:16:19.935979Z"}

tests/test_git_trace2_event_log.py:
+ Added unit tests

sync.py:
+ Changed logging calls to LogDataConfigEvents.

Tested:
$ ./run_tests

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

  Verified config data is looged in trace2 event logs.

Bug: [google internal] b/199758376
Change-Id: I75fd830e90c1811ec28510538c99a2632b104e85
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/317823
Reviewed-by: Josh Steadmon <steadmon@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-09-14 21:36:12 +00:00
1328c35a4d superproject: Provide accurate feedback for user choice
Currently the code would give a message that would appear like the user
have enrolled the experiment regardless of the actual choice. For users
who choose to not enroll in the experiment, we should give them
instructions to override (enable) superproject once instead of how to
disable it, which is what the code already behave.

Bug: [google internal] b/199167992
Change-Id: Iba3314cb510aedf024375a26baa8bc1d5e2846cf
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/317382
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2021-09-08 20:35:42 +00:00
7f8bd85184 superoject: log error message in the 'fmt' field also.
Tested:
+ Verified error messages are being collected.

Bug: [google internal] b/193711236
Change-Id: I6c608a2af332ccd38722b7f83a82e5ac8fa143db
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/317162
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-09-03 00:32:04 +00:00
c63328e5ff docs: Add version for Ubuntu 21.04 Hirsute and Debian 11 Bullseye
* Add footer to the version table, so easier to read and maintain.
* Add version entry for Ubuntu 21.04 Hirsute (non-LTS).
* Add version entry for Debian 11 Bullseye (LTS).

Change-Id: Ic72f911e616b1a13901e56074004f05cdc2c7633
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/313322
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Xin Li <delphij@google.com>
2021-09-03 00:16:43 +00:00
b55769a5c9 superproject: print messages if the manifest has superproject tag.
1) If the manifest has superproject tag (git_master, etc), then
   display error/warning messages (as it is doing today)
2) If the manifest doesn't have superproject tag (nest, chromeos
   manifests), then don't display any error/warning messages about
   superrproject (behave as though user has specified
   --no-use-superproject).
3) Print error/warning messages if --use-superproject passed as
   argument to repo sync.
4) No change in behavior for the repo init command.

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

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

Tested:
$ ./run_tests

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

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

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

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

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

Tested:
$ ./run_tests

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

Bug: [google internal] b/193711236

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

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

This fixes issue with previous_sync_state missing some of the options.

Tested:
$ ./run_tests

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

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

$ repo_dev sync -j 20
  repo sync has finished successfully

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

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

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

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

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

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

Tested:
$ ./run_tests

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

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

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

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

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

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

+ With superproject-override.xml and test it.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Change-Id: I1aa50260f4a00d3cebbd531141e1626825e70127
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312643
Tested-by: Jack Neus <jackneus@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-07-23 18:03:11 +00:00
8e983bbc0f Force a fetch when superproject has a newer SHA1 for remote branch.
For older git-repo versions, we might have only fetched the SHA1
revision that was provided by the project, but have remote branch left
intact as long as they exist. When the remote branch become stale,
some repo operations like rebase would fail, and repo sync would not
correct this situation.

Fix this by tightening the requirement to also require the superproject
provided SHA1 be an ancestor or equal to the tip-of-tree of the remote
branch.

Bug: [google internal] b/193798453
Change-Id: Ie34c5d860dabb1cbd9f822da929088ec69c79cf6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312642
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2021-07-20 23:26:01 +00:00
c34b91c9d8 manifest: Support ignoring local manifests with 'repo manifest'
Currently users need to look up the baseline manifest by loading the
specific manifest file.  This exposes them to the internals of how the
manifest is stored which may potentially be fragile (eg: It was
switched from a symlink pointing at the file in the report to an
actual file with an 'include' tag).

Instead of doing this, we can provide an option to the 'repo manifest'
command which will emit the baseline manifest and decouple users from
the internal manifest details.

Change-Id: I12ee9160feaa591484ae71f404bc529be500ae4e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/311202
Tested-by: Michael Kelly <mkelly@arista.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-07-20 04:56:12 +00:00
0a1f533e28 Add script 'release/update-manpages' to generate manpages
Debian package started to ship manpages for repo since 2.8 [1]
And it's about for one year. So I think it should be upstreamed.

The script depends on help2man, which is available in both debian [2]
and ubuntu [3].

[1] https://tracker.debian.org/news/1150858/accepted-repo-28-1-source-into-unstable
[2] https://tracker.debian.org/pkg/help2man
[3] https://launchpad.net/ubuntu/+source/help2man

Change-Id: Ide2b356d0944ebde34cc96c6d5a782655bd72288
Signed-off-by: Roger Shimizu <rosh@debian.org>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/309782
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-07-16 20:11:41 +00:00
927d29a8af Update manifest_name with the returned manifest name for superproject.
This is similar to smart sync, allowing sync to benefit from the patched
manifest.

Bug: [google internal] b/190688390
Change-Id: I158a80afceca606dcd81ec76b2caede369f7ed03
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/312142
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Xin Li <delphij@google.com>
2021-07-14 22:51:33 +00:00
8db30d686a superproject - More friendly user message when superproject failed.
superproject is going to be default for some users. This change
doesn't fail for repo init or repo sync if source couldn't be synced
because of errors in superproject and superproject=true in the config
file. The commands will fail if --use-superproject is specified on
the command line explicitly.

The error messages are logged with trace2 event logs and will be
monitored.

+ sync - When there are errors with superproject and git_superproject
  says it is fatal failure, sync will exit only when --use-superproject
  option is specified on the command line.

+ init - command doesn't fail *if there are any superproject errors),
  but it will print a warning message and logs message via trace2 event
  logs. For fatal errors, init will exit only when --use-superproject
  option is specified on the command line.

+ All git commands log the command that is being executed so trace2
  event logs will know the manifest, remote url and the branch name.

There is no functional change other than fatal errors are honored with
--use-supeproject option with init/sync commands.

Tested the code with the following commands.

$ ./run_tests -v

Test 1 - sync'ing without errors
--------------------------------
Added the following lines to '~/.repoconfig/config
[repo]
	superproject = true

  $ repo_dev init -u https://android.googlesource.com/platform/manifest -b android-s-beta-2
  NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
  repo: error: git fetch call failed, command: git ['fetch', 'https://android.googlesource.com/platform/superproject', '--depth', '1', '--force', '--no-tags', '--filter', 'blob:none', 'android-s-beta-2:android-s-beta-2'], return code: 128, stderr: fatal: couldn't find remote ref android-s-beta-2

  warning: git update of superproject failed, repo sync will not use superproject to fetch source; while this error is not fatal, and you can continue to run repo sync, please run repo init with the --no-use-superproject option to stop seeing this warning

  Your identity is: Raman Tenneti <rtenneti@google.com>
  If you want to change this, please re-run 'repo init' with --config-name

  repo has been initialized in /usr/local/google/home/rtenneti/work/drive2/android/test

$ repo_dev sync
remote: Total 4 (delta 1), reused 4 (delta 1)
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
/usr/local/google/home/rtenneti/work/drive2/android/aosp/.repo/exp-superproject/925043f706ba64db713e9bf3b55987e2-superproject.git: Initial setup for superproject completed.
...

Test 2 - init and sync fail when --use-superproject option is passed
--------------------------------------------------------------------
$ repo_dev init -u https://android.googlesource.com/platform/manifest -b android-s-beta-2 --use-superproject
remote: Total 57 (delta 16), reused 56 (delta 16)
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
repo: error: git fetch call failed, command: git ['fetch', 'https://android.googlesource.com/platform/superproject', '--depth', '1', '--force', '--no-tags', '--filter', 'blob:none', 'android-s-beta-2:android-s-beta-2'], return code: 128, stderr: fatal: couldn't find remote ref android-s-beta-2

warning: git update of superproject failed, repo sync will not use superproject to fetch source; while this error is not fatal, and you can continue to run repo sync, please run repo init with the --no-use-superproject option to stop seeing this warning

rtenneti@rtenneti2:~/work/drive2/android/test$ repo_dev sync --use-superproject
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
repo: error: git fetch call failed, command: git ['fetch', 'https://android.googlesource.com/platform/superproject', '--depth', '1', '--force', '--no-tags', '--filter', 'blob:none', 'android-s-beta-2:android-s-beta-2'], return code: 128, stderr: fatal: couldn't find remote ref android-s-beta-2

warning: Cannot get project commit ids from manifest
warning: Update of revisionId from superproject has failed, repo sync will not use superproject to fetch the source.  Please resync with the --no-use-superproject option to avoid this repo warning.

Test 3 - git fetch command fails and git command is printed
-----------------------------------------------------------
With config change
$ repo_dev init -u https://android.googlesource.com/platform/manifest -b android-s-beta-2
...
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
.../android/test/.repo/exp-superproject/925043f706ba64db713e9bf3b55987e2-superproject.git: Performing initial setup for superproject; this might take several minutes.
repo: error: git fetch call failed,command: git ['fetch', 'https://android.googlesource.com/platform/superproject', '--depth', '1', '--force', '--no-tags', '--filter', 'blob:none', 'android-s-beta-2:android-s-beta-2'], return code: 128, stderr: fatal: couldn't find remote ref android-s-beta-2

warning: git update of superproject failed, repo sync will not use superproject to fetch source; while this error is not fatal and you can continue to run repo sync please run repo init with the --no-use-superproject option to avoid the repo warning

Your identity is: Raman Tenneti <rtenneti@google.com>
If you want to change this, please re-run 'repo init' with --config-name

repo has been initialized in ....

Test 4 - no superproject tag
-----------------------------
$ repo_dev init -u https://android.googlesource.com/platform/manifest -b pie-dev
...
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
repo error: superproject tag is not defined in manifest: .../android/pie_dev/.repo/manifest.xml
warning: git update of superproject failed, repo sync will not use superproject to fetch source; while this error is not fatal and you can continue to run repo sync please run repo init with the --no-use-superproject option to avoid the repo warning

Your identity is: Raman Tenneti <rtenneti@google.com>
If you want to change this, please re-run 'repo init' with --config-name

repo has been initialized in ...

$ repo_dev sync
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
repo error: superproject tag is not defined in manifest: /usr/local/google/home/rtenneti/work/drive2/android/pie_dev/.repo/manifest.xml
warning: Cannot get project commit ids from manifest
warning: Update of revsionId from superproject has failed. Please resync with --no-use-superproject option to avoid the repo warning.

Bug: [google internal] b/192614798
Bug: [google internal] b/Bug: [google internal] b/192614798

Change-Id: I9a97a0e7d9e609fad151bd7dd9cfc523eaa887cd
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/311502
Reviewed-by: Amith Dsouza <amithds@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-07-14 00:41:36 +00:00
e39d8b36f6 Fix an issue that repo can't see projects declared in a local manifest.
When loading of superproject failed, we were resetting the manifest to
None, and later code would reload the manifest to see if there are
submodules, which would load the non-local manifest, causing sync with
superproject to fail.

Address this by setting the manifest_name to opt.manifest_name instead.

Bug: [google internal] b/189139268
Change-Id: I3616512e1c4b73e7eca0d83fd1fc474b825adbbf
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/311102
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Xin Li <delphij@google.com>
2021-07-13 23:35:07 +00:00
06da9987f6 Gracefully ignore bad remove-project line
Sometimes, we don't care if the remove project is referring to a
non-existing project and we can just ignore it.  This change allows us
to ignore remove-project entries if the project that they refer to
doesn't exist, making them effectively a no-op.

Because this change breaks existing configuration, we allow this to be
configuration controlled using the `optional` attribute in the
remove-project tag.

Change-Id: I6313a02983e81344eadcb4e47d7d6b037ee7420e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/310964
Tested-by: Michael Kelly <mkelly@arista.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-07-08 16:48:21 +00:00
5892973212 sync: dump git output on errors
Bug: https://crbug.com/gerrit/14700
Change-Id: I1ae53dc7f3792b7e8f11d73f706864fb6591eee8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/311142
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-07-04 02:28:00 +00:00
0cb6e92ac5 Add the ability to administratively enroll repo into using superproject.
Repo will remember a choice and an expiration time of the choice, per
user, about whether to use superproject by default. When not specified
from command line and the choice is not expired, repo would use the
user default value.

When a user default value is not present and when the system wide
enable default is provided in git's system configuration, repo would
ask the user for a confirmation which will be valid for two weeks.

git_config.py: Add support for system config. When reading system
	config, we would use --system to avoid hardcoding a path as the
	value may be different on some other distributions.

git_superproject.py: Add a new subroutine, _UseSuperproject(), which
	returns whether superproject should be used and whether it
	is from a user configuration.

	The value is determined in the following order:

	1. If the user specifies either --use-superproject or
	--no-use-superproject, then that choice is being used.

	2. If neither is specified, we would then check the saved value
	(upon repo init) and use that choice when there was a choice.

	3. We then check if there is a saved and unexpired value for
	user's choice in their ~/.gitconfig, and use the unexpired
	choice, if available.

	4. Finally, if all the above didn't give us a decision, and if
	the git system configuration is providing a rollout hint, present
	a prompt to user for their decision and save it in ~/.gitconfig.

subcmds/sync.py: Make use of the new UseSuperproject() provided by
git_superproject.py.

While there also silent stderr from git describe when determining the
version of repo.

Bug: [google internal] b/190688390
Change-Id: Iad3ee03026342ee500e5d65e2f0fa600d7637613
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/309762
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Xin Li <delphij@google.com>
2021-07-01 16:27:43 +00:00
0e776a5837 Fix an issue when syncing with --use-superproject and clone bundles.
It is possible that a clone bundle contained the object referenced by
the branch in the manifest and in the superproject, but not the branch
itself (for example, the branch may be newly created from an existing
branch, or is not vislble to the user downloading the clone bundle).

When --use-superproject is enabled, because we are overriding
revisionExpr with the SHA1 revision provided by the superproject, the
verification step would succeed, but because the expected branch do not
exist, it would confuse git-repo at a later time, as it is expecting the
remote branch to exist in the local clone.

In project.py, fix this by making SetRevisionId() to always remember
the actual branch name and verify it in _CheckForImmutableRevision()
so that we only skip the fetch step when both objects exists locally.

Bug: [google internal] b/191974277
Change-Id: I49d3ca0667f524c8c45f416492faf95b1dd822fb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/310802
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Xin Li <delphij@google.com>
2021-06-30 15:31:15 +00:00
1da6f30579 superproject - don't update the commit ids of projects that have revisionId.
Pinned manifests and release manifest have revisionId set for all
projects. For such projects don't update the commit ids.

Tested the code with the following commands.

$ ./run_tests -v

$ repo_dev sync --use-superproject -j8

$ repo_dev sync -n -c -j32 -m $(pwd)/manifest_7482982.xml

Bug: [google internal] b/191995372
Change-Id: I4681135b1d15f4a63527b6f0356d76ec842485d6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/310582
Reviewed-by: Xin Li <delphij@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-06-29 16:46:07 +00:00
784e16f3aa superproject: Don't exit if superproject tag doesn't exist in manifest.
Don't exit if there are missing commit ids in superproject.

This change implements the following suggestion from delphij@:

"we should note the event (so we know that --use-superproject but there
 were some errors, e.g. manifest didn't specify commit id for some
 reason, or if there is no superproject but --use-superproject is
 used), print out a message telling the use that this is not support,
 but continue as if --no-use-superproject was specified?"

Changes:

superproject:
+ Added git_trace2_event_log as an argument to the constructor.
+ Sync method returns SyncResult a NamedTuple of
  ++ success - True if sync of superproject is successful, or False.
  ++ fatal - True if caller should exit, Or False.
+ UpdateProjectsRevisionId returns UpdateProjectsResult a NamedTuple of
   ++ manifest_path - path name of the overriding manifest file instead
      of None
   ++ fatal - True if caller should exit, Or False
+ _GetAllProjectsCommitIds returns CommitIdsResult a NamedTuple of
  ++ commit_ids - a dictionary with the projects/commit ids on success,
     otherwise None
  ++ fatal - True if caller should exit, Or False
+ Added  _SkipUpdatingProjectRevisionId a helper function to see if a
  project's revision id needs to be updated or not. This function is
  used to exclude projects from local manifest file.
+ Added the following error events into git_trace2_event_log
  ++ If superproject is missing in a manifest
  ++ If there are missing commit ids for projects.

command.py:
+ Deleted unused import - platform
+ Added git_trace2_event_log as a member so all subcmds can log error
  events.

main.py:
+ Initialized git_trace2_event_log as a member of command object.

init.py:
+ Deleted unused import - optparse

init.py:
+ Called sys.exit only if Sync returns exit=True

sync.py:
+ Called sys.exit only if Superproject's UpdateProjectsRevisionId returns
  exit=True
+ Reloaded the manifest only if manifest path is returned by
  UpdateProjectsRevisionId. If not, fall back to the old way of doing
  repo sync.

test_git_superproject:
+ Added code to verify error events are being logged.
+ Added a test for no superproject tag
+ Added test for UpdateProjectsRevisionId not updating the revision id
  with the commit ids.

Tested the code with the following commands.

+ Positive test case with aosp-master.
  $ repo_dev init -u persistent-https://android.git.corp.google.com/platform/manifest -b master --use-superproject
  NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
  .../android/aosp/.repo/exp-superproject/925043f706ba64db713e9bf3b55987e2-superproject.git: Initial setup for superproject completed.

  Your identity is: Raman Tenneti <rtenneti@google.com>
  If you want to change this, please re-run 'repo init' with --config-name

  repo has been initialized in .../android/aosp

  $ repo_dev sync -j40 --use-superproject
  remote: Total 12 (delta 4), reused 12 (delta 4)
  NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
  .../android/aosp/.repo/exp-superproject/925043f706ba64db713e9bf3b55987e2-superproject.git: Initial setup for superproject completed.
  ...
  repo sync has finished successfully.

+ Negative test case without superproject tag.
  $ repo_dev sync -j40 --use-superproject
  NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
  repo error: superproject tag is not defined in manifest: .../android/aosp/.repo/manifest.xml
  error: Cannot get project commit ids from manifest
  error: Update of revsionId from superproject has failed. Please resync with --no-use-superproject option
  ...
  Checking out: 100% (1022/1022), done in 3.589s
  repo sync has finished successfully.

+ Test for missing commit_id for a project.
  $ repo_dev sync -j40 --use-superproject
  NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
  .../android/aosp/.repo/exp-superproject/925043f706ba64db713e9bf3b55987e2-superproject.git: Initial setup for superproject completed.
  error: please file a bug using go/repo-bug to report missing commit_ids for: ['build/blueprint']
  error: Update of revsionId from superproject has failed. Please resync with --no-use-superproject option
  ...
  Checking out: 100% (1022/1022), done in 3.364s
  repo sync has finished successfully.

$ ./run_tests -v
  ...
  ...== 164 passed in 2.87s ==...

Bug: [google internal] b/189371541
Change-Id: I5ea49f87e8fa41be590fc0c914573e16c8cdfcfa
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/309162
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-06-16 04:48:35 +00:00
b8c84483a5 repo: improve duplicate default check
If one default is totally empty, we don't need to fail.

BUG=b:187795796
TEST=unit tests

Change-Id: Id226a7a7cd183dbdee58f4681b84885cc9211375
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/309102
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Jack Neus <jackneus@google.com>
2021-06-15 18:06:13 +00:00
d58d0dd3bf commands: pass settings via __init__
Instead of setting properties on the instantiated command, pass them
via the constructor like normal objects.

Change-Id: I8787499bd2be68565875ffe243c3cf2024b36ae7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/309324
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-06-15 06:08:13 +00:00
d88b369a42 commands: document the "event_log" class attribute
Add some notes explaining why it's instantiated at the Command class
level and not individual objects.

Change-Id: Ib8081bb8480e85f6d3dfc23953c6bbc6ecc64934
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/309323
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-06-15 06:08:00 +00:00
4f21054c28 commands: document the "common" class attribute
Switch it to uppercase to make it clear it's a constant, and add
documentation so its usage is clear.

Change-Id: I6d281a66a90b5908b3131585c9945e88cfe815ea
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/309322
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-06-15 06:07:37 +00:00
5ba2120362 repo: properly handle NoneType in Default/Remote equality checks
BUG=none
TEST=none

Change-Id: I4ccdbbc7ba4b6f6e20c6959db1b46fdb44ea2819
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/308982
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Jack Neus <jackneus@google.com>
2021-06-11 13:54:32 +00:00
78f4dd3138 superproject: add projects from local manifest to local::<filename> group.
With repo sync --use-superproject, don't update the commit ids of every project
that comes from local manifest.

Tested the code with the following commands.

$ ./run_tests -v

+ Test with local.xml

1. repo init --use-superproject -u persistent-https://googleplex-android.git.corp.google.com/a/platform/manifest

2. cd .repo
cp -r /google/src/head/depot/google3/wireless/android/build_tools/translations/pipeline/local_manifests local_manifests
cd ..

local$ time repo_dev sync --use-superproject
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
.../local/.repo/exp-superproject/feb2c2847da5e274f3d530d5ab438af8-superproject.git: Initial setup for superproject completed.
...

Bug: [google internal] b/189360443
Bug: [google internal] b/189139268
Bug: https://crbug.com/gerrit/14499
Change-Id: Ideaf268c294e9b500b2b9726ffbd733dd8d63004
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/308822
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2021-06-10 00:16:36 +00:00
fc7aa90623 trace2_event_log: Added logging of error events.
Added error event in preperation for superproject to log errors.

Testing:
+ Unit tests
   ./run_tests -v

Bug: [google internal] b/189371541
Change-Id: Ife1dd28d52d9e9925b7b34ae913f8eb5fa19037c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/308863
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-06-09 14:24:20 +00:00
50c91ecf4f superproject: revert not updating commit ids if remote is different.
superproject supports multiple remotes. Get all commit ids
from superproject for all projects that are in the manifest.

$ ./run_tests -v

Bug: [google internal] b/186395810
Change-Id: I6edce3918853a7a3a65aec5528e6a43a544eff53
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/308862
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-06-08 22:43:32 +00:00
816d82c010 run_tests: fix pytest selection inside tox venv
Finding the "right" pytest is challenging.  In Debian, `pytest` is
Python 2 while `pytest-3` is the Python 3 version ... but only when
outside of a virtualenv.  Inside of a virtualenv (e.g. the ones that
tox creates), we always want `pytest`.

Change-Id: Ic1fe84c10f06227bceeb9baad6a3c4598bbe9860
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303802
Reviewed-by: Peter Kjellerstedt <peter.kjellerstedt@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-06-03 15:27:51 +00:00
2b37fa3f26 superproject: change the warning message to say it is beta.
$ repo_dev init --use-superproject -u https://android.googlesource.com/platform/manifest
remote: Total 3 (delta 0), reused 3 (delta 0)
NOTICE: --use-superproject is in beta; report any issues to the address described in `repo version`
...

$ ./run_tests -v

Bug: [google internal] b/189946009
Change-Id: Ifb3ef266a72b67f3c4a2a3ac2033b10e03b789d4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/308522
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-06-03 14:38:09 +00:00
a3b2edf1af Drop support for Python 3.5
Running repo with Python 3.5 fails due to the use of the encoding
parameter to subprocess.run(). There are also f-strings being used in
some of the tests.

This drops support for these systems:
* Ubuntu Xenial: released Apr 2016, EOS Apr 2021, EOL Apr 2024
* Debian Stretch: released Jun 2017, EOL Jun 2022

So the minimum required distros now are:
* Ubuntu Bionic: released Apr 2018 w/Python 3.6
* Debian Buster: released Jul 2019 w/Python 3.7

Change-Id: I1144f7ab6f882b10cac0131982df081fe4ac44f9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303363
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
2021-06-03 11:34:17 +00:00
e253b43e17 superproject: require git version 2.28.0 or higher.
Tested the code with the following commands.

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

+ Tested for the wrong git version.
  $ repo_dev init --use-superproject -u https://android.googlesource.com/platform/manifest
  WARNING: --use-superproject is experimental and not for general use
  superproject requires a git version 2.28 or later
  error: git update of superproject failed

$ ./run_tests -v

Bug: https://crbug.com/gerrit/14617
Bug: [google internal] b/189846687
Change-Id: I5cd4158ea29b3b3c8c81234f4e818165de346e63
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/308442
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-06-03 00:30:10 +00:00
5d58c18146 tests: Make the tests pass for Python < 3.8
Before Python 3.8, xml.dom.minidom sorted the attributes of an element
when writing it to a file, while later versions output the attributes
in the order they were created. Avoid these differences by sorting the
attributes for each element before comparing the generated manifests
with the expected ones.

Bug: https://crbug.com/gerrit/14382
Change-Id: Ie2597727afcc48f9063a7261ad970e8a549f0587
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303326
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-26 13:36:20 +00:00
d177609cb0 sync: Sync submodules correctly again
Commit b4429439 (sync: refactor main fetch loop) broke syncing of
submodules with sync-s="true". The first `repo sync` would just fetch
the superrepo, while the second `repo sync` would fetch the submodules.
This was due to the new _FetchMain() function not passing back the
all_projects variable that it had modified.

Change-Id: Ie8187cde7bb894e4e9a6b76c2aed83873d9f69a4
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/307065
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-20 16:23:10 +00:00
b16b9d26bd project: fix error display when output_redir is disabled
We always pass in output_redir when syncing, but that's the common case:
there are a few situations (like `repo init`) where we don't pass in a
buffer, and if any errors show up in that case, we'd crash.  Rely on the
print function to handle this logic for us.

Bug: https://crbug.com/gerrit/14568
Change-Id: I8cd47e82329797ffc42534418a3dfbd8429205be
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/307222
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-20 06:15:38 +00:00
993af5e136 superproject: Use bugurl from contactinfo in the missing commits error message.
+ In XmlManifest._Unload set 'bugurl' to Wrapper().BUG_URL.
+ contactinfo returns a namedtuple.
+ bug_url can be accessed as self._manifest.contactinfo.bugurl.

Tested the code with the following commands.

$ ./run_tests -v

Added contactinfo tag to default.xml and verified that bugurl is used.

Bug: [google internal] b/186220520.
Change-Id: Iaafd6465e072b2e47a0a0b548bf6cb608a0b0a04
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/306342
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-18 15:35:54 +00:00
339f2df1dd ssh: rewrite proxy management for multiprocessing usage
We changed sync to use multiprocessing for parallel work.  This broke
the ssh proxy code as it's all based on threads.  Rewrite the logic to
be multiprocessing safe.

Now instead of the module acting as a stateful object, callers have to
instantiate a new ProxyManager class that holds all the state, an pass
that down to any users.

Bug: https://crbug.com/gerrit/12389
Change-Id: I4b1af116f7306b91e825d3c56fb4274c9b033562
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305486
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
2021-05-10 21:16:06 +00:00
19e409c818 ssh: move proxy usage to the sync subcommand
The only time we really need ssh proxies is when we want to run many
connections and reuse them.  That only happens when running sync.
Every other command makes at most two connections, and even then it's
only one or none.  So the effort of setting up & tearing down ssh
proxies isn't worth it most of the time.

The big reason we want to move this logic to sync is that it's now
using multiprocessing for parallel work.  The current ssh proxy code
is all based on threads, which means none of the logic is working
correctly.  The current ssh design makes it hard to fix when all of
the state lives in the global/module scope.

So the first step to fixing this is top move the setup & teardown to
the one place that really needs it: sync.  No other commands will use
proxies anymore, just direct connections.

Bug: https://crbug.com/gerrit/12389
Change-Id: Ibd351acdec39a87562b3013637c5df4ea34e03c6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305485
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-10 21:10:29 +00:00
4a58100251 launcher: bump version for new release
Change-Id: I1f204bb1e5ce6b13c623215236deef01efbc0f6c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305822
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-10 17:10:04 +00:00
0e8828c47b Handle 400 error code when attempting to fetch clone bundle.
Gitlab returns a 400 error when trying to fetch clone.bundle
from a repository containing the git-repo tool. The repo
launcher doesn't then fall back to not using a clone.bundle
file and the repo init fails.

Change-Id: Ia3390d0638ef9a39fb2fab84625b269d28caf1cc
Signed-off-by: Craig Northway <cnorthway@codeaurora.org>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305382
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-10 16:34:34 +00:00
23ea754524 sync: added --no-use-superproject to disable superproject.
Tested the code with the following commands.

$ ./run_tests -v

$ repo_dev sync -c -j8 --no-use-superproject
Fetching: 100% (1041/1041), done in 1m22.743s

$ repo_dev sync -c -j8 --use-superproject
WARNING: --use-superproject is experimental and not for general use
..

Bug: [google internal] b/187459275
Change-Id: I3f4269df38cd24a21723e8b2be5a1f013e7b5a91
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305682
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-08 00:04:00 +00:00
f907ced0fe sync: Recommend using --no-use-superproject if sync fails.
If superproject was not available for a branch, then the next
repo sync would also fail because --use-superproject is
remembered across repo init. In such cases, hoping the hint to
to use --no-use-superproject will help.

Tested the code with the following commands and by forcing
a failure.

$ ./run_tests -v

Bug: [google internal] b/187459275
Change-Id: Ie250812b7ba83afc230b5b1d154ba11f245f8b8a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305622
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-05-07 20:15:50 +00:00
b44294395f sync: refactor main fetch loop
This is a large chunk of code that is largely isolated.  Move it into
a class method to make it easier to manage & reason about, and in a
follow up CL, easier to scope.

Bug: https://crbug.com/gerrit/12389
Change-Id: I0c69d95a9e03478d347b761580b2343bffa012d5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305484
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
2021-05-06 19:46:09 +00:00
5291eafa41 ssh: move all ssh logic to a common place
We had ssh logic sprinkled between two git modules, and neither was
quite the right home for it.  This largely moves the logic as-is to
its new home.  We'll leave major refactoring to followup commits.

Bug: https://crbug.com/gerrit/12389
Change-Id: I300a8f7dba74f2bd132232a5eb1e856a8490e0e9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305483
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-06 19:09:16 +00:00
8e768eaaa7 git_command: switch version caches to functools
Simplifies the code a bit to use the stdlib cache helper.

Change-Id: I778e90100ce748a71cc3a5a5d67dda403334315e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305482
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-06 18:36:25 +00:00
2f8fdbecde manifest_xml: cleanup of contactinfo test for readability with f-strings.
Tested the code with the following commands.

$ ./run_tests -v

Bug: [google internal] b/186220520.
Change-Id: I1c0f8958ff4c615707eec218250e8de753ec6562
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305282
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-05-05 15:59:58 +00:00
148e1ce81a sync: fix recursive fetching
Commit b2fa30a2b8 ("sync: switch network
fetch to multiprocessing") accidentally changed the variable passed to
the 2nd fetch call from |missing| to |to_fetch| due to a copy & paste
of the earlier changed logic.  Undo that to fix git submodule fetching.

Bug: https://crbug.com/gerrit/14489
Change-Id: I627954f80fd2e80d9d5809b530aa6b0ef9260abb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305262
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 22:43:09 -04:00
219431e1c9 sync: fix recursive fetching
Commit b2fa30a2b8 ("sync: switch network
fetch to multiprocessing") accidentally changed the variable passed to
the 2nd fetch call from |missing| to |to_fetch| due to a copy & paste
of the earlier changed logic.  Undo that to fix git submodule fetching.

Bug: https://crbug.com/gerrit/14489
Change-Id: I627954f80fd2e80d9d5809b530aa6b0ef9260abb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305262
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-05 02:42:23 +00:00
32ca6687ae git_config: hoist Windows ssh check earlier
The ssh master logic has never worked under Windows which is why this
code always returned False when running there (including cygwin).  But
the OS check was still done while holding the threading lock.  While
it might be a little slower than necessary, it still worked.

The switch from the threading module to the multiprocessing module
changed global behavior subtly under Windows and broke things: the
globals previously would stay valid, but now they get cleared.  So
the lock is reset to None in children workers.

We could tweak the logic to pass the lock through, but there isn't
much point when the rest of the code is still disabled in Windows.
So perform the platform check before we grab the lock.  This fixes
the crash, and probably speeds things up a few nanoseconds.

This shouldn't be a problem on Linux systems as the platform fork
will duplicate the existing process memory (including globals).

Bug: https://crbug.com/gerrit/14480
Change-Id: I1d1da82c6d7bd6b8cdc1f03f640a520ecd047063
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305149
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 19:49:58 -04:00
5ba80d404c git_config: hoist Windows ssh check earlier
The ssh master logic has never worked under Windows which is why this
code always returned False when running there (including cygwin).  But
the OS check was still done while holding the threading lock.  While
it might be a little slower than necessary, it still worked.

The switch from the threading module to the multiprocessing module
changed global behavior subtly under Windows and broke things: the
globals previously would stay valid, but now they get cleared.  So
the lock is reset to None in children workers.

We could tweak the logic to pass the lock through, but there isn't
much point when the rest of the code is still disabled in Windows.
So perform the platform check before we grab the lock.  This fixes
the crash, and probably speeds things up a few nanoseconds.

This shouldn't be a problem on Linux systems as the platform fork
will duplicate the existing process memory (including globals).

Bug: https://crbug.com/gerrit/14480
Change-Id: I1d1da82c6d7bd6b8cdc1f03f640a520ecd047063
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305149
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 23:49:29 +00:00
1c3f57e8f1 manifest_xml: initial support for <contactinfo>
It will be used to let manifest authors self-register contact info.
This element can be repeated, and any later entries will clobber
earlier ones. This would allow manifest authors who extend
manifests to specify their own contact info.

It would have 1 required attribute: bugurl.
"bugurl" specifies the URL to file a bug against the manifest owner.

<contactinfo bugurl="bug-url"/>

TODO: This CL only implements the parsing logic and further work
will be in followup CLs.

Tested the code with the following commands.

$ ./run_tests tests/test_manifest_xml.py
$ ./run_tests -v

Bug: [google internal] b/186220520.
Change-Id: I47e765ba2dab5cdf850191129f4d4cd6b803f451
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305203
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-04 22:36:01 +00:00
05638bf771 sync: use manifest_name passed in
Commit fb527e3f52 ("sync: create dedicated
manifest project update func") refactored code from the main body into a
dedicated method.  The manifest_name was passed as an argument, but never
used it, and instead reaches back out to the command line options.  This
ignores the logic in the main loop where manifest_name might have changed
(like when using smart sync).

Change-Id: I4b84638fbb10c2b6f8f4b555e1475b0669c2daf4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305148
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 20:00:50 +00:00
c99322a6a9 sync: switch to multiprocessing.Event
We've switched most of this command over to multiprocessing and off
of _threading, so do the Event object too.  The APIs are the same
between the modules, so we shouldn't need to update anything else.

Change-Id: I52d31f1c6ef2bcbe7bbc1dd1add79a8d5d08784a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305147
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 20:00:41 +00:00
14208f4c93 sync: fix logic error with linkfile errors
Make sure err_update_linkfiles is always initalized.

Bug: https://crbug.com/gerrit/11008
Change-Id: I7bdd91f82507608ef967daf0fa0f9c859454e19f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305146
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 20:00:20 +00:00
2ee0a62db0 release-process: document the rate limiting in automatic updates
We check for updates only once per day, so clarify the docs.

Change-Id: Ib669ca6ebc67bc13204996fa40e1a3a82012295e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305145
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 19:37:48 +00:00
0ae9503a86 sync: fix print error when handling server error
When converting this logic from print() to the output buffer, this
error codepath should have dropped the use of the file= redirect.

Bug: https://crbug.com/gerrit/14482
Change-Id: Ib484924a2031ba3295c1c1a5b9a2d816b9912279
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305142
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 12:48:42 -04:00
c177f944d9 subcmds: force consistent help text format
We're inconsistent with help text as to whether it uses title case and
whether it ends in a period.  Add a test to enforce a standard, and use
the style that Python optparse & argparse use themselves (e.g. with the
--help option): always lowercase, and never trailing period.

Change-Id: Ic1defae23daeac0ac9116aaf487427f50b34050d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305144
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 16:40:53 +00:00
aedd1e5ef0 sync: fix print error when handling server error
When converting this logic from print() to the output buffer, this
error codepath should have dropped the use of the file= redirect.

Bug: https://crbug.com/gerrit/14482
Change-Id: Ib484924a2031ba3295c1c1a5b9a2d816b9912279
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/305142
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 16:40:28 +00:00
5a41b0be01 superproject: skip updating commit ids if remote's fetchUrl don't match.
Tested the code with the following commands.

$ ./run_tests -v

+ Test with local.xml
  $ repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b master --use-superproject --partial-clone --clone-filter=blob:limit=10M && mkdir -p .repo/local_manifests && (gcertstatus -quiet=true || gcert) && ln -s /google/src/head/depot/google3/wireless/android/build_tools/aosp/manifests/mirror-aosp-master-with-vendor/local.xml  .repo/local_manifests/local.xml

  $ repo_dev sync -c -j8

+ Test without local.xml
  $ repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b master --partial-clone --clone-filter=blob:limit=10M --repo-rev=main --use-superproject
  $ repo_dev sync -c -j8

Bug: [google internal] b/186395810
Change-Id: Id618113a91c12bcb90a30a3c23d3d6842bcb49e1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304942
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-05-04 15:32:23 +00:00
d68ed63328 init/sync: add --no-tags to match --tags
While this provides a way to undo earlier command line options (e.g.
`repo sync --tags --no-tags`) which can be helpful for scripting &
automation, this more importantly allows the user to override the
manifest settings for syncing tags from a project.

Bug: https://crbug.com/gerrit/12401
Change-Id: Id4c36cd82e6ca7cb073b5d63a09f6c7ccdebba83
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304904
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 11:32:17 +00:00
7356114d90 add --no-current-branch option to invert --current-branch
For most commands, this is more about providing a way to undo earlier
command line options (e.g. `repo info -c --no-current-branch`) which
can be helpful for scripting & automation.  But for the sync command,
this is helpful to undo the setting that exists in the manifest itself.

With this in place, tweak the sync current_branch_only logic to only
apply the manifest settings when the user hasn't specified a command
line option.

Bug: https://crbug.com/gerrit/12401
Change-Id: I21e2384624680cc740d1b5d1e49c50589d2fe6a0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304903
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-04 11:31:48 +00:00
b8e09ea1d6 harmonize --current-branch short option across subcommands
We're inconsistent with the short option for this flag:
* gitc-init: <none as -c is already used>
* info:      -b
* init:      -c
* overview:  -b
* sync:      -c
* upload:   --cbr

Since info & overview are not as heavily used as the others, switch
them from -b to -c.  We leave -b in as a hidden alias for now.

Similarly, switch upload from --cbr to just -c.  A lot of people
use --cbr, so we leave this as a hidden alias for now too.

Ideally gitc-init wouldn't use -c, but that ship has sailed, and
we're more likely to deprecate gitc entirely at this point.

This provides a consistent set of options across subcommands.

Bug: https://crbug.com/gerrit/12401
Change-Id: Iec249729223866fe1ea0ebabed12ca851cc38b35
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304902
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-03 16:57:23 +00:00
feb28914bd superproject: Don't update the commit ids of projects if remote is different.
1) Skip setting the revision id (commit id) for the projects whose
   remote doesn't match superproject's remote.
2) exp-superproject/superproject_override.xml includes local_manfiest's
   projects. When we load this XML, don't reload projects from local.xml
   (otherwise we will get duplicate projects errors).

Tested the code with the following commands.

$ ./run_tests -v

+ Test with local.xml
  $ repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b master --use-superproject --partial-clone --clone-filter=blob:limit=10M && mkdir -p .repo/local_manifests && (gcertstatus -quiet=true || gcert) && ln -s /google/src/head/depot/google3/wireless/android/build_tools/aosp/manifests/mirror-aosp-master-with-vendor/local.xml  .repo/local_manifests/local.xml

  $ repo_dev sync -c -j8

+ Test without local.xml
  $ repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b master --partial-clone --clone-filter=blob:limit=10M --repo-rev=main --use-superproject
  $ repo_dev sync -c -j8

Bug: [google internal] b/186395810
Change-Id: I4e9d4ac2d94a9fc0cef0ccd787b6310758009e86
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304882
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-03 05:13:23 +00:00
d1f3e149df upload: search local projects in parallel
Search for project branches to upload in parallel.  This can cut the
lookup time in half for large projects.  We still run the actual hooks
in serial once we have the list of projects to process, but we would
need to rethink things quite a bit before we could handle running them
in parallel too.

Change-Id: I8da0cbc5010566aa860e1a158f3dc07f0709dcff
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304842
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-02 00:06:32 +00:00
29626b4f46 project: fix m/ generation when switching manifest branches
We were updating the per-checkout m/ pseudo ref when syncing, but we
only created the common m/ redirect when initializing a project for
the first time.  This is fine unless the user switches the manifest
branch in an existing project, then we never create that redirect.

Bug: https://crbug.com/gerrit/14468
Change-Id: I5325e7e602dcb4ce150bef258901ba5e9fdea461
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304822
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-02 00:05:55 +00:00
3b038cecc4 upload: include the project in error messages
When running upload across multiple projects, include the project in
any error messages that come up.  This lets users figure out where
the problem might be.

Change-Id: I09470c9a1b512baf910d6d97b747816d1a6f3a87
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304783
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-05-02 00:05:49 +00:00
a590e640a6 Update copyfile and linkfile if manifest updated
Currently, copyfiles and linkfiles which marked by
"<copyfile/>" and "<linkfile/>" in manifest will
be created by first exec 'repo sync'.
But if some "<copyfile/>" or "<linkfile/>" are removed
in manifest, then 'repo sync', these removed item
dest can not be removed in the sourcecode workspace.

This patch is intent to fix this issue, by save a
'copy-link-files.json' in .repo and then compared with
new dest path when next sync. If any "<copyfile/>" or
"<linkfile/>" were removed, the dest path will be
removed in sourcecode at the same time.

Bug: https://crbug.com/gerrit/11008
Change-Id: I6b7b41e94df0f9e6e52801ec755951a4c572d05d
Signed-off-by: jiajia tang <tangjiajia@xiaomi.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304202
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-05-01 13:26:08 +00:00
f69c7ee318 manifest_xml: ban use of newlines in paths
There should be no valid use of these anywhere, so just ban them
to make things easier for people.

Bug: https://crbug.com/gerrit/14156
Bug: https://crbug.com/gerrit/14200
Change-Id: I8d2cf988c510c98194c43a329a2b9bf313a3f0a8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304662
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-30 05:54:11 +00:00
aabf79d3f0 sync: Fix a corner case when both superproject and depth used.
When depth is used, we would fetch only SHA1 when superproject is
used, as the result, only the manifest branch is being recorded,
and commands like repo start would fail.

Fix this by saving the upstream branch value in the overlay
manifest and add the upstream branch to fetch list.

Bug: [google internal] b/185951360
Change-Id: Ib36f56067723f2572ed817785b31cc928ddfec0a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304562
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Xin Li <delphij@google.com>
2021-04-29 19:05:47 +00:00
a1cd770d56 help/version: sprinkle bug report URL around
Make it a bit easier for people to locate bug reporting info.

Change-Id: If9c8939c84ebd52eb96b353c1797afa25868bb85
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303943
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2021-04-26 21:43:43 +00:00
cd89ec147a sync: Fix exception in an exsiting clone (without partial-clone).
Default the partial_clone_exclude argument to an empty set.

Fixes the following report by Emil Medve.

With this change (up to v2.14.1), on an existing "normal" clone (without partial-clone options) I'm seeing this traceback during `repo selfupdate`:

Traceback (most recent call last):

  File ".../.repo/repo/main.py", line 630, in <module>
    _Main(sys.argv[1:])
  File ".../.repo/repo/main.py", line 604, in _Main
    result = run()
  File ".../.repo/repo/main.py", line 597, in <lambda>
    run = lambda: repo._Run(name, gopts, argv) or 0
  File ".../.repo/repo/main.py", line 261, in _Run
    result = cmd.Execute(copts, cargs)
  File ".../.repo/repo/subcmds/selfupdate.py", line 54, in Execute
    if not rp.Sync_NetworkHalf():
  File ".../.repo/repo/project.py", line 1091, in Sync_NetworkHalf
    if self.name in partial_clone_exclude:
TypeError: argument of type 'NoneType' is not iterable

$ ./run_tests -v

Change-Id: I71e744e4ef2a37b13aa9ba42eba3935e78c4e40a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/304082
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-04-22 18:00:32 +00:00
d41eed0b36 sync: fix missing import for -q
Some refactors during review dropped this import when it was reworked,
but it's still needed when using the --quiet setting.

Change-Id: I6d9302ef5a056e52415ea63f35bad592b9dfa75d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303942
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-21 15:37:16 +00:00
d2b086bea9 init: restore default --manifest-name
The merge of the repo & init parser missed this default.

When running `repo init ...` in an existing checkout but w/out the -m
option, then repo would error out complaining that -m is required when
it didn't do this before.

Change-Id: I58035d48cc413b5d373702b9dc3b9ecd3fd1e900
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303945
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2021-04-21 04:45:59 +00:00
6823bc269d sync: cleanup sleep+retry logic a bit
Make sure we print a message whenever we retry so it's clear to the
user why repo is pausing for a long time, and why repo might have
passed even though it displayed some errors earlier.

Also unify the sleep logic so we don't have two independent methods.
This makes it easier to reason about.

Also don't sleep if we're in the last iteration of the for loop.  It
doesn't make sense to and needlessly slows things down when there are
real errors.

Bug: https://crbug.com/gerrit/12494
Change-Id: Ifceace5b2dde75c2dac39ea5388527dd37376336
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303402
Reviewed-by: Sam Saccone 🐐 <samccone@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-19 16:42:06 +00:00
ad8aa69772 sync: only print error.GitError, don't raise that exception.
In _FetchOne & _CheckOne, only print error.GitError exception,
but other exceptions are still thrown

Fixes the GitError exceptions from /usr/lib/python3.8/multiprocessing/pool.py
exiting the repo sync.

Tested the code with the following commands and verified repo sync
continues after fetch error because of an invalid SHA1.

$ ./run_tests -v

$ python3 ~/work/repo/git-repo/repo sync -m manifest_P21623846.xml -j32
...
error.GitError: Cannot fetch platform/vendor/google_devices/redbull/proprietary update-ref: fatal: d5a99e518f09d6abb0c0dfa899594e1ea6232459^0: not a valid SHA1
....

An error like the following when jobs=1
  error.GitError: Cannot checkout platform/vendor/qcom/sdm845/proprietary/qcrilOemHook: Cannot initialize work tree for platform/vendor/qcom/sdm845/proprietary/qcrilOemHook

Bug: https://crbug.com/gerrit/14392
Change-Id: I8922ad6c07c733125419f5698b0f7e32d70c7905
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303544
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-04-15 22:43:07 +00:00
b5d075d04f command: add a helper for the parallel execution boilerplate
Now that we have a bunch of subcommands doing parallel execution, a
common pattern arises that we can factor out for most of them.  We
leave forall alone as it's a bit too complicated atm to cut over.

Change-Id: I3617a4f7c66142bcd1ab030cb4cca698a65010ac
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/301942
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
2021-04-15 05:10:16 +00:00
b8bf291ddb tests: Make ReviewableBranchTests.test_smoke work with git < 2.28.0
Bug: https://crbug.com/gerrit/14380
Change-Id: Id015bd98b008e1530ada2c7e4332c67e8e208e25
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303325
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-04-14 16:22:52 +00:00
233badcdd1 list: fix help grammar
Change-Id: Ia642e38532173d59868e0101cc098eab706d715e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303302
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-14 15:25:53 +00:00
0888a083ec help: switch from formatter module to textwrap
Since Python has deprecated the formatter module, switch to the textwrap
module instead for reflowing text.  We weren't really using any other
feature anyways.

Verified by diffing the output before & after the change and making sure
it was the same.

Then made a few tweaks to tighten up the output.

Change-Id: I0be1bc2a6661a311b1a4693c80d0f8366320ba55
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303282
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-14 01:00:51 +00:00
e2effe11a5 list: add option to show non-checkedout projects too
Currently, list only shows projects that exist in the checkout, and
doesn't offer any way to list all projects in the manifest (based on
the current settings, or on the options passed to list).  This seems
to be the opposite of what (at least some) users expect, so let's
add an option to show all of them regardless of checkout state.

Change-Id: I94bbdc5bd0ff2a411704fa215e7fc2b60fa3360e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/301263
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-13 22:42:32 +00:00
151701e85f progress: hide progress bar when --quiet
We want progress bars in the default output mode, but not when the
user specifies --quiet.  Add a setting to the Progress bar class so
it takes care of not displaying anything itself rather than having
to update every subcommand to conditionally setup & call the object.

Change-Id: I1134993bffc5437bc22e26be11a512125f10597f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303225
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-13 22:25:26 +00:00
9180a07b8f command: make --verbose/--quiet available to all subcommands
Add new CommonOptions entry points to move the existing --jobs to,
and relocate all --verbose/--quiet options to that.  This provides
both a consistent interface for users as well as for code.

Change-Id: Ifaf83b88872421f4749b073c472b4a67ca6c0437
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303224
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-13 22:25:17 +00:00
f32f243ff8 init: Added --partial-clone-exclude option.
partial-clone-exclude option excludes projects during
partial clone. This is a comma-delimited project names
(from manifest.xml). This option is persisted and it
is used by the sync command.

A project that has been unparital'ed will remain unpartial if
that project's name is specified in the --partial-clone-exclude
option. The project name should match exactly.

Added
$ ./run_tests -v

Bug: [google internal] b/175712967
"I can't "unpartial" my androidx-main checkout"

$ rm -rf androidx-main/
$ mkdir androidx-main/
$ cd androidx-main/
$ repo_dev init -u https://android.googlesource.com/platform/manifest -b androidx-main --partial-clone --clone-filter=blob:limit=10M -m default.xml
$ repo_dev sync -c -j8

+ Verify a project is partial
$ cd frameworks/support/
$ git config -l | grep  'partial'

+ Unpartial a project.
$ /google/bin/releases/android/git_repack/git_unpartial

+ Verify project is unpartial
$ git config -l | grep  'partial'
$ cd ../..

+ Exclude the project from being unparial'ed after init and sync.
$ repo_dev init -u https://android.googlesource.com/platform/manifest -b androidx-main --partial-clone --clone-filter=blob:limit=10M --partial-clone-exclude="platform/frameworks/support,platform/frameworks/support-golden" -m default.xml

+ Verify project is unpartial
$ cd frameworks/support/
$ git config -l | grep  'partial'
$ cd ../..
$ repo_dev sync -c -j8
$ cd frameworks/support/
$ git config -l | grep  'partial'
$ cd ../..

+ Remove the project from exclude list and verify that project is partially cloned.
$ repo_dev init -u https://android.googlesource.com/platform/manifest -b androidx-main --partial-clone --clone-filter=blob:limit=10M --partial-clone-exclude= -m default.xml
$ repo_dev sync -c -j8
$ cd frameworks/support/
$ git config -l | grep  'partial'

Change-Id: Id5dba418eba1d3f54b54e826000406534c0ec196
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303162
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-04-13 15:47:10 +00:00
49de8ef584 sync: add separate --jobs options for different steps
The number of jobs one wants to run against the network tends to
factor differently from the number of jobs one wants to run when
checking out local projects.  The former is constrained by your
internet connection & server limits while the later is constrained
by your local computer's CPU & storage I/O.  People with beefier
computers probably want to keep the network/server jobs bounded a
bit lower than the local/checkout jobs.

Change-Id: Ia27ab682c62c09d244a8a1427b1c65acf0116c1c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/302804
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-09 15:58:56 +00:00
a1051d8baa init: organize command line options a bit
We've grown a lot of options in here and it's hard to make sense of
them.  Add more groups to try and make it easier to pick things out.

Change-Id: I6b9dc0e83f96137f974baf82d3fb86992b857bd2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/302803
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-09 15:58:45 +00:00
65af2602b5 sync: add progress bar to garbage collection phase
This can take a few seconds, if not a lot more, so add a progress bar
so users understand what's going on.

Change-Id: I5b4b54c1bbb9ec18728f979521310f7087afaa5c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/302802
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-09 15:58:21 +00:00
347f9ed393 sync: rework selfupdate logic
The current logic has a downside in that it doesn't sync to the latest
signed version available if the latest commit itself is unsigned.  This
can come up when using the "main" branch as it is sometimes signed, but
often not as it's holding the latest merged commits.  When people use
the main branch, it's to get early testing on versions tagged but not
yet released, and we don't want them to get stuck indefinitely on that
old version of repo.

For example, this series of events:
* "stable" is at v2.12.
* "main" is tagged with v2.13.
* early testers use --repo-rev main to get v2.13.
* new commits are merged to "main".
* "main" is tagged with v2.14.
* new commits are merged to "main".
* devs who had synced in the past to test v2.13 are stuck on v2.13.
  repo sees "main" is unsigned and so doesn't try to upgrade at all.

The only way to get unwedged is to re-run `repo init --repo-rev main`,
or to manually sync once with repo verification disabled, or for us to
leave "main" signed for a while and hope devs will sync in that window.

The new logic is that whenever changes are available, we switch to the
latest signed tag.  We also replace some of the duplicated verification
code in the sync command with the newer wrapper logic.  This handles a
couple of important scenarios inaddition to above:
* rollback (e.g. v2.13.8 -> v2.13.7)
* do not trash uncommitted changes (in case of ad-hoc testing)
* switch tag histories (e.g. v2.13.8 -> v2.13.8-cr1)

Change-Id: I5b45ba1dd26a7c582700ee3711f303dc7538579b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/300122
Reviewed-by: Jonathan Nieder <jrn@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-09 03:16:45 +00:00
9a734a3975 init: merge subcmd & wrapper parsers
These are manually kept in sync which is a pain.  Have the init
subcmd reuse the wrapper code directly.

Change-Id: Ica73211422c64377bacc9bb3b1d1a8d9d5f7f4ca
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/302762
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-09 01:04:32 +00:00
6a2f4fb390 repo init: Added --no-partial-clone and made it persist. Bumped version to 2.14.
Saved the repo.partialclone when --no-partial-clone option is passed
to init, so repo sync will honor the no-partial-clone option.

$ ./run_tests -v

Bug: [google internal] b/175712967

$ mkdir androidx-main && cd androidx-main
$ repo init -u https://android.googlesource.com/platform/manifest -b androidx-main --partial-clone --clone-filter=blob:limit=10M
$ repo sync -c -j32
$ cd frameworks/support/ && /google/bin/releases/android/git_repack/git_unpartial
$ git config -l | grep  'partialclonefilter=blob'

Observe partialclone is not enabled.

$ cd ../..
$ repo init -u https://android.googlesource.com/platform/manifest -b androidx-main
$ repo sync -c -j32
$ cd frameworks/support/ && git config -l | grep  'partialclonefilter=blob'

Observe partialclone is enabled.

$ /google/bin/releases/android/git_repack/git_unpartial

Observe partialclone is not enabled.

$ cd ../..
$ repo_dev init -u https://android.googlesource.com/platform/manifest -b androidx-main --no-partial-clone
$ repo sync -c -j32
$ cd frameworks/support/ && git config -l | grep  'partialclonefilter=blob'

Observe partialclone is not enabled.

Change-Id: I4400ad7803b106319856bcd0fffe00bafcdf014e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/302122
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-04-05 05:53:19 +00:00
beea5de842 tox: enable python 3.5 & 3.9 testing
We still support Python 3.5, so make sure it keeps working.

Change-Id: I150158a656b26de6d733316a68a2cbb8b5b99716
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299625
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-01 14:57:05 +00:00
bfbcfd9045 run_tests: fix exit code handling
We need to pass back an int, not a CompletedProcess object.  Switch to
check=False so we don't throw an exception on failure -- we're already
showing pytest's stderr, and will return the non-zero status.

Change-Id: Ib0d3862a09a3963f25025f39a8e34419cf2a54df
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299624
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-01 14:56:57 +00:00
74317d3b01 setup: bump major version
We don't keep this up-to-date in general, but might as well keep
the major version in sync.

Change-Id: I20908005b3b393d384da0ef9342d7c9d094550cb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299622
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-01 14:56:47 +00:00
b2fa30a2b8 sync: switch network fetch to multiprocessing
This avoids GIL limitations with using threads for parallel processing.

This reworks the fetch logic to return results for processing in the
main thread instead of leaving every thread to do its own processing.

We have to tweak the chunking logic a little here because multiprocessing
favors batching over returning immediate results when using a larger value
for chunksize.  When a single job can be quite slow, this tradeoff is not
good UX.

Bug: https://crbug.com/gerrit/12389
Change-Id: I0f0512d15ad7332d1eb28aff52c29d378acc9e1d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298642
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-01 14:52:57 +00:00
d246d1fee7 grep: add --jobs support
Use multiprocessing to run in parallel.  When operating on multiple
projects, this can greatly speed things up.  Across 1000 repos, it
goes from ~40sec to ~16sec with the default -j8.

The output processing does not appear to be a significant bottle
neck -- it accounts for <1sec out of the ~16sec runtime.  Thus we
leave it in the main thread to simplify the code.

Change-Id: I750b72c7711b0c5d26e65d480738fbaac3a69971
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297984
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-04-01 14:43:19 +00:00
bec4fe8aa3 prune: add --jobs support
Use multiprocessing to run in parallel.  When operating on multiple
projects, this can greatly speed things up.  Across 1000 repos, it
goes from ~10sec to ~4sec with the default -j8.

This only does a simple conversion over to get an easy speedup.  It
is currently written to collect all results before displaying them.
If we refactored this module more, we could have it display results
as they came in.

Change-Id: I5caf4ca51df0b7f078f0db104ae5232268482c1c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298643
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-31 16:28:24 +00:00
ddab0604ee forall: handle missing project refs better
If the project exists, but the ref the manifest wants doesn't exist,
don't throw an error (and abort the process in general).  This can
come up with a partially synced tree: the manifest is up-to-date,
but not all the projects have yet been synced.

Bug: https://crbug.com/gerrit/14289
Change-Id: Iba97413c476544223ffe518198c900c2193a00ed
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/301262
Reviewed-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-25 23:08:51 +00:00
2ae44d7029 sync: imply -c if --use-superproject option is used.
Tested the code with the following commands.

$ ./run_tests -v

Bug: [google internal] b/183232698
Bug: https://crbug.com/gerrit/13707

$ repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b master --partial-clone --clone-filter=blob:limit=10M --repo-rev=main --use-superproject
$ repo_dev sync --use-superproject
$ repo_dev sync
  real	0m8.046s
  user	0m2.866s
  sys	0m2.457s

Second time repo sync took only 8 seconds and verified by printing that
urrent_branch_only is True in project.py's Sync_NetworkHalf function.

Change-Id: Ic48efb23ea427dfa36e12a5c49973d6ae776d818
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/301182
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-03-24 15:17:19 +00:00
d1e4fa7015 start: add a --HEAD alias
This makes it easy to use --H as a shortcut, and kind of matches the
use of storing HEAD as the revision.

Change-Id: I590bf488518f313e7a593853140316df98262d7e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/301163
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-24 00:32:04 +00:00
323b113f55 forall/list: delete spurious "
Change-Id: I6995d48be1d8fc5d93f4b9fa617fad70b5b3429f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/300902
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-19 21:13:49 +00:00
8367096d02 superproject: Added --depth=1 argument to git fetch command.
Tested the code with the following commands.

$ ./run_tests -v

Without --depth

$ time repo_dev init -u sso://googleplex-android.git.corp.google.com/platform/manifest -b master --repo-rev=main --use-superproject
real    6m48.086s
user    3m27.281s
sys     1m1.386s

With --depth=1
$ time repo_dev init -u sso://googleplex-android.git.corp.google.com/platform/manifest -b master --repo-rev=main --use-superproject

real    2m49.637s
user    2m51.458s
sys     0m39.108s

From dwillemsen@:
 "For me it's the difference between 9m28s using the current code and
  16s using --depth=1 while fetching the superproject."

Bug: [google internal] b/180451672
Bug: https://crbug.com/gerrit/13707
Change-Id: I1c3e4aef4414c4e9dd259fb6e4619da0421896b0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/300922
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-03-19 21:10:39 +00:00
d34af28ac2 forall: allow interactive commands with -j1
Historically forall has been interactive since it ran in serial.
Recent rework in here dropped that to enable parallel processing.
Restore support for interactive commands when running -j1 or with
an explicit --interactive option.

Bug: https://crbug.com/gerrit/14256
Change-Id: I502007186f771914cfd7830846a4e1938b5e1f38
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/300722
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-18 22:13:01 +00:00
a5b40a2845 repo: Add a new "command" event type to git trace2 logging in repo.
Add a new "event": "command", which is emitted at when all command
arguments have been processed.

Additional fields:

"name": Name of the primary command (ex: repo, git)

"subcommands"': List of the sub-commands once command-line arguments
                are processed

Examples:

Command: repo --version

Event: {"event": "command", <common fields>,
        "name": "repo",
	"subcommands": ["version"]
	}

Bug: [google internal] b/178507266
Testing:
- Unit tests
- Verified repo git trace2 logs had expected data

Change-Id: I825bd0ecedee45135382461a4ba10f987f09aef3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/300343
Reviewed-by: Ian Kasprzak <iankaz@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-03-18 14:58:24 +00:00
511a0e54f5 sync: fix reporting of failed local checkouts
The refactor to multiprocessing broke status reporting slightly when
checking out projects.  Make sure we mark the step as failed if any
of the projects failed, not just when --fail-fast is set.

Change-Id: I0efb56ce83b068b2c334046df3fef23d797599c9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299882
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-15 16:54:23 +00:00
8da7b6fc65 bash-completion: initial import based on CrOS version
We've had a limited version of this in CrOS for a long time.  There's
nothing CrOS specific about it, so lets move it to the repo project so
everyone can utilize it.

Change-Id: I04cd94610c1100f3afcd2baf8c8e7ab13e589490
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299202
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-15 16:54:21 +00:00
0458faa502 manifest: allow toplevel project checkouts
Re-allow checking out projects to the top of the repo client checkout.
We add checks to prevent checking out files under .repo/ as that path
is only managed by us, and projects cannot inject content or settings
into it.

Bug: https://crbug.com/gerrit/14156
Bug: https://crbug.com/gerrit/14200
Change-Id: Id6bf9e882f5be748442b2c35bbeaee3549410b25
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299623
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-12 16:31:14 +00:00
68d5d4dfe5 document the new manifest restrictions on name & path settings
Bug: https://crbug.com/gerrit/14156
Change-Id: I473edab1173e6a266d0754c29d5dc7ff761f1359
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299403
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-12 16:30:37 +00:00
a3794e9c6f prune: minor optimization & robustification
If the current project doesn't have any local branches, then there's
nothing to prune, so return right away.  This avoids running a few
git commands when we aren't actually going to use the results, and
it avoids checking repository validity.  Since we aren't going to do
anything in here, no need to check it.

Change-Id: Ie9d5c75a954e42807477299f3e5a63a92fac138b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299742
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-12 05:28:06 +00:00
080877e413 superproject: pass groups to ToXml method.
Added the following methods to XmlManifest class.
+ GetDefaultGroupsStr() - return 'default,platform-' + platform.system().lower()
+ GetGroupsStr() - Same as gitc_utils.py's _manifest_groups func.

+ Replaced gitc_utils.py's_manifest_groups calls with GetGroupsStr.
+ Used the above methods to get groups in command.py::GetProjects
  and part of init.py.

TODO: clean up these funcs to take structured group data more instead
      of passing strings around everywhere that need parsing.

Tested the code with the following commands.

$ ./run_tests -v

Tested the sync code by using repo_dev alias and pointing to this CL
and verified prebuilts/fullsdk-linux directory has all the folders.

Tested repo init and repo sync with --use-superproject and without
--use-superproject argument.

$ repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b androidx-main  --partial-clone --clone-filter=blob:limit=10M --repo-rev=main --use-superproject

$ repo_dev sync -c -j32

Bug: [google internal] b/181804931
Bug: https://crbug.com/gerrit/13707
Change-Id: Ia98585cbfa3a1449710655af55d56241794242b6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299422
Reviewed-by: Jonathan Nieder <jrn@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-03-11 01:24:52 +00:00
9888accb0c project: fix diff printing with embedded %
The recent commit 84230009ee ("project:
make diff tools synchronous") broke repo diff if it includes % formats.
Add an explicit format string to fix.

Bug: https://crbug.com/gerrit/14208
Change-Id: Ie255a43c5b767488616b2b3dd15abc18f93bfab2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299402
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-09 17:00:02 +00:00
5a4c8fde17 init: expose --worktree option
There's a few rough edges here still, but no known corruption ones,
so open it up a bit for people to experiment with.

Bug: https://crbug.com/gerrit/11486
Change-Id: I81e0122ab6d3e032c546c8239dd4f03740676e80
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299242
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-09 16:59:59 +00:00
835a34bdb9 Log repo.* config variables in git trace2 logger.
Bug: [google internal] b/181758736
Testing:
- Unit tests
- Verified repo git trace2 logs had expected data

Change-Id: I9af8a574377bd91115f085808c1271e9dee16a36
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299182
Tested-by: Ian Kasprzak <iankaz@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
2021-03-08 17:32:09 +00:00
ef99ec07b4 superproject: Display status messages during repo init/sync.
Superproject objects accept the optional argument “quiet”.
The following progress messages are displayed if quiet is false.

Displayed the following message whenever we find we have to make a new
folder (aka new remote), because if you started with repo init android
and later do googleplex-android that is when it will be slow.

"<location>: Performing initial setup for superproject; this might take
several minutes.".

After fetch completion, added the following notification:
"<location>: Initial setup for superproject completed."

Tested the code with the following commands.

$ ./run_tests -v

Tested the sync code by using repo_dev alias and pointing to this CL.

$ repo_dev init -u persistent-https://googleplex-android.git.corp.google.com/platform/manifest -b rvc-dev  --partial-clone --clone-filter=blob:limit=10M --repo-rev=main  --use-superproject

Bug: [google internal] b/181178282
Bug: https://crbug.com/gerrit/13707
Change-Id: Ia7fb85c6fb934faaa90c48fc0c55e7f41055f48a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299122
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-03-04 20:07:52 +00:00
934cb0a849 tests: fix duplicate method from copy & paste error
Change-Id: Ib748c61b1e65aee6dff8b97a9753d14c470a827f
Reported-by: Daniel Kutik <daniel.kutik@lavawerk.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299002
Reviewed-by: Daniel Kutik <daniel.kutik@lavawerk.com>
Reviewed-by: Ian Kasprzak <iankaz@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-04 16:17:11 +00:00
3c0931285c project: fix variable typo
Bug: https://crbug.com/gerrit/11293
Reported-by: Daniel Kutik <daniel.kutik@lavawerk.com>
Change-Id: I37bac58aa1dc9ecc10e29253d14ff9e6fb42427c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298942
Reviewed-by: Ian Kasprzak <iankaz@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-03 16:45:21 +00:00
5413397204 manifest: relax include name rules for user-specified path
Allow the user to specify relative or absolute or any other funky
path that they want when using `repo init` or `repo sync`.  Our
goal is to restrict the paths in the remote manifest git repo we
cloned from the network, not protect the user from themselves.

Bug: https://crbug.com/gerrit/14156
Change-Id: I1ccfb2a6bd1dce2bd765e261bef0bbf0f8a9beb6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298823
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-02 03:18:57 +00:00
13cb7f799d forall: greatly speed up processing overhead
With the recent commit 0501b29e7a
("status: Use multiprocessing for `repo status -j<num>` instead of
threading"), the limitation with project serialization no longer
applies.  It turns out that ad-hoc logic is expensive.  In the CrOS
checkout (~1000 projects w/8 jobs by default), it adds about ~7sec
overhead to all invocations.  With a fast nop run:
	time repo forall -j8 -c true
This goes from ~11sec to ~4sec -- more than 50% speedup.

Change-Id: Ie6bcccd21eef20440692751b7ebd36c890d5bbcc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298724
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-01 15:58:06 +00:00
819c73954f forall: simplify arg passing to worker children
The ProjectArgs function can be inlined which simplifies it quite a
bit.  We shouldn't need the custom exception handling here either.
This also makes the next commit easier to review.

Change-Id: If3be04f58c302c36a0f20b99de0f67e78beac141
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298723
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-01 15:58:06 +00:00
179a242caa forall: move nested func out to the class
This is in preparation for simplifying the jobs support.  The nested
function is referenced in the options object which can't be pickled,
so pull it out into a static method instead.

Change-Id: I01d3c4eaabcb8b8775ddf22312a6e142c84cb77d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298722
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-01 15:57:32 +00:00
31fabeed54 download: handle shared projects a bit better
If a manifest checksout a project multiple times, repo download isn't
able to accurately pick the right project.  We were just picking the
first result which could be a bit random for the user.  If we hit that
situation, check if the cwd is one of the projects, and if it isn't,
we emit an error and tell the user it's an ambiguous request.

Bug: https://crbug.com/gerrit/13070
Change-Id: Id1059b81330229126b48c7312569b37504808383
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298702
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-01 15:57:17 +00:00
76844ba292 project: skip clone bundles when we've already initialized the object dir
The clone bundle logic assumes there is a one-to-one mapping between the
projects/ and project-objects/ trees.  When using shared projects (where
we checkout different branches from the same project), this would lead us
to fetching the same clone bundle multiple times.  Automatically skip the
clone bundle logic if the project-objects/ dir already exists.

Bug: https://crbug.com/gerrit/10993
Change-Id: I82c6fa1faf8605fd56c104fcea2a43dd4eecbce4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298682
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-03-01 15:57:12 +00:00
6d1faa1db3 git_refs: fix crash with binary . files in .git/refs/
On macOS, the Finder app likes to poop .DS_Store files in every path
that the user browses.  If the user pokes around the .git/ tree, it
could generate a .DS_Store file in there too.  When repo goes to read
all the local refs, it tries to decode this binary file as UTF-8 and
subsequently crashes.

Since paths that begin with . are not valid refs, ignore them like we
already do with paths that end in .lock.  Also bump the check up to
ignore dirs that match since that follows the git rules: they apply
to any component in its path, not just the final path (name).

We don't implement the full valid ref algorithm that git employs as
it's a bit complicated, and we only really need to focus on what will
practically show up locally.

Bug: https://crbug.com/gerrit/14162
Change-Id: I6519f990e33cc58a72fcb00c0f983ad3285ace3d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298662
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
2021-02-28 16:07:24 +00:00
4510be51c1 git_command: pass GIT_DIR on Windows with /
When using Git under Windows, it seems that Git doesn't always parse
GIT_DIR correctly when it uses the Windows \ form, but does when it
uses / only.

For example, when using worktrees:
$ GIT_DIR='C:\Users\vapier\Desktop\repo\breakpad\tools\test\.git' git worktree list
fatal: not a git repository: ..\..\.repo\worktrees\linux-syscall-support.git\worktrees\test
$ GIT_DIR='C:/Users/vapier/Desktop/repo/breakpad/tools/test/.git' git worktree list
C:/Users/vapier/Desktop/repo/breakpad/.repo/worktrees/linux-syscall-support.git  fd00dbbd0c06 (detached HEAD)
..\..\..\..\..\src\src\third_party\lss\.git                                      fd00dbbd0c06 (detached HEAD)
..\..\..\..\..\tools\test\.git                                                   fd00dbbd0c06 (detached HEAD)

Change-Id: I666c03ae845ecb55d7f9800731ea6987d3e7f401
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298622
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-28 16:07:20 +00:00
a29424ea6d manifest: validate project name & path and include name attributes
These attribute values are used to construct local filesystem paths,
so apply the existing filesystem checks to them.

Bug: https://crbug.com/gerrit/14156
Change-Id: Ibcceecd60fa74f0eb97cd9ed1a9792e139534ed4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298443
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-28 16:07:12 +00:00
a00c5f40e7 manifest: refactor the filesystem checking logic for more reuse
This function is currently written with copyfile & linkfile in mind.
Generalize the logic & function arguments slightly so we can reuse
in more places that make sense.

This changes the validation logic slightly too in that we no longer
allow "." for the dest attribute with copyfile & linkfile, nor for
the src attribute with copyfile.  We already rejected those later on
when checking against the active filesystem, but now we reject them
a little sooner when parsing.

The empty path check isn't a new requirement exactly -- repo used to
crash on it, so it was effectively blocked, but now we diagnosis it.

Bug: https://crbug.com/gerrit/14156
Change-Id: I0fdb42a3da60ed149ff1997c5dd4b85da70eec3d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298442
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-28 16:07:12 +00:00
6093d99d13 checkout: add --jobs support
Use multiprocessing to run in parallel.  When operating on multiple
projects, this can speed things up.  Across 1000 repos, it goes from
~9sec to ~5sec with the default -j8.

Change-Id: Ida6dd565db78ff7bac0ecb25d2805e8a1bf78048
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297982
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-27 19:56:24 +00:00
ebf04a4404 sync: switch local checkout to multiprocessing
This avoids GIL limitations with using threads for parallel processing.
In a CrOS checkout with ~1000 repos, the nop case goes from ~6 sec down
to ~4 sec with -j8.  Not a big deal, but shows that this actually works
to speed things up unlike the threading model.

This reworks the checkout logic to return results for processing in the
main thread instead of leaving every thread to do its own processing.

Bug: https://crbug.com/gerrit/12389
Change-Id: I143e5e3f7158e83ea67e2d14e5552153a874248a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298063
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-27 19:55:14 +00:00
8dbc07aced abandon/start: add --jobs support
Use multiprocessing to run in parallel.  When operating on multiple
projects, this can greatly speed things up.  Across 1000 repos, it
goes from ~30sec to ~3sec with the default -j8.

Change-Id: I0dc62d704c022dd02cac0bd67fe79224f4e34095
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297484
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
2021-02-27 19:45:14 +00:00
8d2a6df1fd progress: include execution time summary
We're already keeping tracking of the start time, so might as
well use it to display overall execution time for steps.

Change-Id: Ib4cf8b2b0dfcdf7b776a84295d59cc569971bdf5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298482
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-26 17:16:29 +00:00
ceba2ddc13 sync: superproject - support for switching hosts and switching branches.
+ superproject will be fetched into a directory with the name
  “<remote name>-superproject.git” instead of the current
  “superproject.git” folder.

+ Deleted  _Clone method and added _Init method.

+ _Init method will do “git init --bare <remote>-superproject.git”.
  It will create the folder and set up a bare repository in
  <remote>-superproject.git folder.

+ _Fetch method, will pass <remote url>, <branch> arguments.
  Moved the --filter argument from “git clone” to “git fetch”.
  _Fetch method will execute the following command to fetch
  superproject. Added --no-tags argument.

  master:  git fetch <remote url> --force --no-tags --filter blob:none
  branch:  git fetch <remote url> --force --no-tags --filter blob:none \
           <branch>:<branch>

+ Performance improvements for aosp-master
  ++ repo init performance improved from 35 seconds to 17 seconds.
  ++ repo init --use-superproject is around 5 to 7 secsonds slower.
  ++ repo sync --use-superproject is around 3 to 4 minutes faster.

Tested the code with the following commands.

$ ./run_tests -v

Tested the sync code by using repo_dev alias and pointing to this CL.

$ time repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b master --partial-clone --clone-filter=blob:limit=10M --repo-rev=main --use-superproject
...
  real	0m20.648s
  user	0m8.046s
  sys	0m3.271s

+ Without superproject
$ time repo init -u sso://android.git.corp.google.com/platform/manifest -b master --partial-clone --clone-filter=blob:limit=10M --repo-rev=main
  real	0m13.078s
  user	0m9.783s
  sys	0m2.528s

$ time repo_dev sync -c -j32 --use-superproject
...
  real	15m7.072s
  user	110m7.216s
  sys	20m17.559s

+ Without superproject
$ time repo sync -c -j32
...
  real	19m25.644s
  user	91m56.331s
  sys	20m59.170s

Bug: [google internal] b/180492484
Bug: [google internal] b/179470886
Bug: [google internal] b/180124069
Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707

Change-Id: Ib04bd7f1e25ceb75532643e58ad0129300ba3299
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297702
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-02-25 20:45:26 +00:00
45ad1541c5 grep: move nested func out to the class
This is in preparation for adding jobs support.  The nested function
is referenced in the options object which can't be pickled, so pull
it out into a static method instead.

Change-Id: I280ed2bf26390a0203925517a0d17c13053becaa
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297983
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 20:13:33 +00:00
7b586f231b sync: capture all git output by default
The default sync output should show a progress bar only for successful
commands, and the error output for any commands that fail.  Implement
that policy here.

Bug: https://crbug.com/gerrit/11293
Change-Id: I85716032201b6e2b45df876b07dd79cb2c1447a5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297905
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 20:13:18 +00:00
fbb95a4342 progress/sync: include active number of jobs
Provide a bit more info to users that things are actively running.

Bug: https://crbug.com/gerrit/11293
Change-Id: Ie8eeaa8804d1ca71cf5c78ad850fa2d17d26208c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297904
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 20:13:18 +00:00
4e05f650e0 progress: always enable always_print_percentage
The idea for skipping some progress updates was to avoid spending
too much time on the progress bar itself.  Unfortunately, for large
projects (100s if not 1000s) of repos, we get into the situation
with large/slow checkouts that we skip showing updates when a repo
finishes, but not enough repos finished to increase the percent.

Since the progress bar should be relatively fast compared to the
actual network & local dick operations, have it show an update
whenever the caller requests it.  A test with ~1000 repos shows
that the progress bar in total adds <100ms.

Bug: https://crbug.com/gerrit/11293
Change-Id: I708a0c4bd923c59c7691a5b48ae33eb6fca4cd14
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297903
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 20:13:18 +00:00
23882b33fe init: support -b HEAD as a shortcut to "the default"
When people switch to non-default branches, they sometimes want to
switch back to the default, but don't know the exact name for that
branch.  Add a -b HEAD shortcut for that.

Change-Id: I090230da25f9f5a169608115d483f660f555624f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297843
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 20:12:51 +00:00
92304bff00 project: fix http error retry logic
When sync moved to consume clone output, it merged stdout & stderr,
but the retry logic in this function is based on stderr only.  Move
it over to checking stdout.

Change-Id: I71bdc18ed25c978055952721e3a768289d7a3bd2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297902
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 20:12:42 +00:00
adbd01e0d3 tests: fix init subcmd after url change
My recent 401c6f0725 ("init: make
--manifest-url flag optional") commit broke the unittest.

Change-Id: I19ad0e8c8cbb84ab5474ebc370e00acfe957e136
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298223
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 17:07:11 +00:00
37ac3d626f tests: refactor manifest tests
The XmlManifestTests class is getting to be large and we're only
adding more to it.  Factor out the core logic into a new TestCase
so we can reuse it to better group more tests.

Change-Id: I5113444a4649a70ecfa8d83d3305959a953693f7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298222
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 17:06:56 +00:00
55d6a5a3a2 sync: use superproject if manifest's config has superproject enabled.
If --use-superproject is passed as argument to "repo init", then
--use-superproject need not be specified during "repo sync".

Tested the code with the following commands.

$ time repo_dev sync -c -j32
...
WARNING: --use-superproject is experimental and not for general use

Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Change-Id: Ibb33f3038a2515f74a6c4f7cb785d354b26ee680
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298102
Tested-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Ian Kasprzak <iankaz@google.com>
2021-02-25 16:35:53 +00:00
6db4097f31 docs: add warnings about repos data model
For people coming across these docs and thinking that repo's methods
are good to replicate, add a note warning them against doing so.

Change-Id: I443a783794313851a6e7ba1c39baebac988bff9a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/298164
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-25 15:48:03 +00:00
f0925c482f platform_utils: delete unused FileDescriptorStreams APIs
Now that we've converted the few users of this over to subprocess APIs,
we don't need this anymore.  It's been a bit hairy to maintain across
different operating systems, so there's no desire to bring it back.

Using multiprocessing Pool to batch things has been working better in
general anyways.

Change-Id: I10769e96f60ecf27a80d8cc2aa0d1b199085252e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297682
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-24 01:45:57 +00:00
be24a54d9c sync: update event is_set API
Python 3 renamed this method from isSet to is_set.

Change-Id: I8f9bb0b302d55873bed3cb20f2d994fa2d082157
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297742
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-23 17:56:49 +00:00
c87c1863b1 git_command: switch process capturing over to subprocess
Now that these code paths are all synchronous, there's no need to run
our own poll loop to read & pass thru/save output.  Delete all of that
and just let the subprocess module take care of it all.

Change-Id: Ic27fe71b6f964905cf280ce2b183bb7ee46f4a0d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297422
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-23 00:36:51 +00:00
69b4a9cf21 diff: add --jobs support
Use multiprocessing to run diff in parallel.

Change-Id: I61e973d9c2cde039d5eebe8d0fe8bb63171ef447
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297483
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
2021-02-23 00:31:27 +00:00
fbab6065d4 forall: rewrite parallel logic
This fixes intermingling of parallel jobs and simplifies the code
by switching to subprocess.run.  This also provides stable output
in the order of projects by returning the output as a string that
the main loop outputs.

This drops support for interactive commands, but it's unclear if
anyone was relying on that, and the default behavior (-j2) made
that unreliable.  If it turns out someone still wants this, we can
look at readding it.

Change-Id: I7555b4e7a15aad336667292614f730fb7a90bd26
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297482
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-22 22:58:30 +00:00
15e807cf3c forall: improve pool logic
Use a pool contextmanager to take care of the messy details like
properly cleaning it up when aborting.

Change-Id: I264ebb591c2e67c9a975b6dcc0f14b29cc66a874
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297243
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-22 22:51:46 +00:00
7c871163c8 status: improve parallel execution stability
The status command runs a bunch of jobs in parallel, and each one
is responsible for writing to stdout directly.  When running many
noisy jobs in parallel, output can get intermingled.  Pass down a
StringIO buffer for writing to so we can return the entire output
as a string so the main job can handle displaying it.  This fixes
interleaved output as well as making the output stable: we always
display results in the same project order now.  By switching from
map to imap, this ends up not really adding any overhead.

Bug: https://crbug.com/gerrit/12231
Change-Id: Ic18b07c8074c046ff36e306eb8d392fb34fb6eca
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297242
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
2021-02-22 22:51:34 +00:00
6a2400a4d0 command: unify --job option & default values
Extend the Command class to support adding the --jobs option to the
parser if the command declares it supports running in parallel.  Also
pull the default value used for the number of local jobs into the
command module so local commands can share it.

Change-Id: I22b0f8d2cf69875013cec657b8e6c4385549ccac
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297024
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
2021-02-22 22:51:07 +00:00
c5bbea8db3 git_command: make execution synchronous
Every use of GitCommand in the tree just calls Wait as soon as it's
instantiated.  Move the bulk of the logic into the init path to make
the call synchronous to simplify.  We'll cleanup the users of the
Wait API to follup commits -- having this split makes it easier to
track down regressions.

Change-Id: I1e8c519efa912da723749ff7663558c04c1f491c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297244
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-20 08:41:10 +00:00
5d9c4972e0 use simpler super() magic
Python 3 has a simpler super() style so switch to it to make the
code a little simpler and to stop pylint warnings.

Change-Id: I1b3ccf57ae968d56a9a0bcfc1258fbd8bfa3afee
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297383
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-19 20:06:20 +00:00
057905fa1d error: fix pickling of all exceptions
Make sure all our custom exceptions can be pickled so that if they
get thrown in a multiprocess subprocess, we don't crash & hang due
to multiprocessing being unable to pickle+unpickle the exception.

Details/examples can be seen in Python reports like:
https://bugs.python.org/issue13751

Change-Id: Iddf14d3952ad4e2867cfc71891d6b6559130df4b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297382
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-19 20:06:03 +00:00
401c6f0725 init: make --manifest-url flag optional
Since the --manifest-url flag is always required when creating a new
checkout, allow the url to be specified via a positional argument.
This brings it a little closer to the `git clone` UI.

Change-Id: Iaf18e794ae2fa38b20579243d067205cae5fae2f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297322
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2021-02-18 20:38:47 +00:00
8c1e9e62a3 gitc_utils: rewrite to use multiprocessing
This is the only code in the tree that uses GitCommand asynchronously.
Rewrite it to use multiprocessing.Pool as it makes the code a little
bit easier to understand and simpler.

Change-Id: I3ed3b037f24aa1e9dfe8eec9ec21815cdda7678a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297143
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
2021-02-18 07:11:07 +00:00
84230009ee project: make diff tools synchronous
These are the only users in the tree that process the output as it's
produced.  All others capture all the output first and then process
the results.  However, these functions still don't fully return until
it's finished processing, and these funcs are in turn used in other
synchronous code paths.  So it's unclear whether anyone will notice
that it's slightly slower or less interactive.  Let's try it out and
see if users report issues.

This will allow us to simplify our custom GitCommand code and move it
over to Python's subprocess.run, and will help fix interleaved output
when running multiple commands in parallel (e.g. `repo diff -j8`).

Change-Id: Ida16fafc47119d30a629a8783babeba890515de0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297144
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2021-02-18 03:54:30 +00:00
f37b9827a9 git_command: rework stdin handling
We only provide input to GitCommand in one place, so inline the logic
to be more synchronous and similar to subprocess.run.  This makes the
code simpler and easier to understand.

Change-Id: Ibe498fedf608774bae1f807fc301eb67841c468b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297142
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-17 15:15:16 +00:00
c47a235bc5 trim redundant pass statements
Clean up a few linter warnings.

Change-Id: I531d0263a202435d32d83d87ec24998f4051639c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297062
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-16 19:23:00 +00:00
f307916f22 git_command: use subprocess.run for version info
The code is a bit simpler & easier to reason about.

Change-Id: If125ea7d776cdfa38a0440a2b03583de079c4839
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297023
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-16 16:26:43 +00:00
fb21d6ab64 sync: use subprocess.run to verify tags
The code is a bit simpler & easier to reason about.

Change-Id: I149729c7d01434b08b58cc9715dcf0f0d11201c2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297022
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-16 16:26:41 +00:00
21dce3d8b3 init: added --use-superproject option to clone superproject.
Added --no-use-superproject to repo and init.py to disable use of
manifest superprojects.

Replaced the term "sha" with "commit id".

Added _GetBranch method to Superproject object.

Moved shared code between init and sync into SyncSuperproject function.
This function either does git clone or git fetch. If git fetch fails
it does git clone.

Changed Superproject constructor to accept manifest, repodir and branch
to avoid passing them to multiple functions as argument.

Changed functions that were raising exceptions to return either True
or False.

Saved the --use-superproject option in config as repo.superproject.
Updated internal-fs-layout.md document.

Updated the tests to work with the new API changes in Superproject.

Performance for the first time sync has improved from 20 minutes to
around 15 minutes.

Tested the code with the following commands.

$ ./run_tests -v

Tested the sync code by using repo_dev alias and pointing to this CL.

$ repo init took around 20 seconds longer because of cloning of superproject.

$ time repo_dev init -u sso://android.git.corp.google.com/platform/manifest -b master --partial-clone --clone-filter=blob:limit=10M --repo-rev=main --use-superproject
...
real	0m35.919s
user	0m21.947s
sys	0m8.977s

First run
$ time repo sync --use-superproject
...
real	16m41.982s
user	100m6.916s
sys	19m18.753s

No difference in repo sync time after the first run.

Bug: [google internal] b/179090734
Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707

Change-Id: I12df92112f46e001dfbc6f12cd633c3a15cf924b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296382
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-02-11 18:59:29 +00:00
e3315bb49a diffmanifests/sync: simplify repodir lookup
We have access to repodir on the command object itself, so we don't
need to pull it indirectly out of the manifest object.

Change-Id: I8688fb1c84979825efa966dc787e78c6f7ba3823
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296542
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-11 02:38:53 +00:00
38867fb6d3 git_config: add SetBoolean helper
A little sugar simplifies the code a bit.

Change-Id: Ie2b8a965faa9f9ca05c7be479d03e8e073cd816d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296522
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-02-11 01:48:12 +00:00
ce64e3d47b superproject: Pass branch to git ls-tree.
Tested the code with the following commands.

$ ./run_tests -v

Bug: [google internal] b/179702819
Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I7d2b609ac2f927c94701757aa1502ba236afe7c0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296342
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-02-08 23:43:34 +00:00
8d43dea6ea sync: pass --bare option when doing git clone of superproject.
Changed "git pull" to "git fetch" as we are using --bare option. Used the
following command to fetch:
  git fetch origin +refs/heads/*:refs/heads/* --prune

Pass --branch argument to Superproject's UpdateProjectsRevisionId function.

Returned False/None when directories don't exist instead of raise
GitError exception from _Fetch and _LsTree functions. The caller of Fetch
does Clone if Fetch fails.

Tested the code with the following commands.

$ ./run_tests -v

Tested the init and sync code by copying all the repo changes into my Android
AOSP checkout and running repo sync with --use-superproject option.

Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I3e441ecdfc87c735f46eff0eb98efa63cc2eb22a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296222
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-02-08 17:34:55 +00:00
1fd7bc2438 sync: superproject performance changes.
After updating all project’s revsionIds with the SHAs from superproject,
write the updated manifest into superproject_override.xml file. Reload
that file for future Reloads. This file is created in exp-superproject
directory.

Moved most of the code that is superproject specific into
git_superproject.py and wrote test code.

If git pull fails, did a git clone of the superproject.

We saw performance gains for consecutive repo sync's. The time to sync
went down from around 120 secs to 40 secs when repo sync is executed
consecutively.

Tested the code with the following commands.

$ ./run_tests -v tests/test_git_superproject.py
$ ./run_tests -v

Tested the sync code by copying all the repo changes into my Android
AOSP checkout and doing a repo sync --use-superproject twice.

First run
$ time repo sync --use-superproject
...
real	21m3.745s
user	97m59.380s
sys	19m11.286s

After two consecutive sync runs
$ time repo sync -c -j8 --use-superproject
real	0m39.626s
user	0m29.937s
sys	0m38.155s

Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Tested-by: Raman Tenneti <rtenneti@google.com>

Change-Id: Id79a0d7c4d20babd65e9bd485196c6f8fbe9de5e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296082
Reviewed-by: Ian Kasprzak <iankaz@google.com>
Tested-by: Raman Tenneti <rtenneti@google.com>
2021-02-07 22:25:38 +00:00
b5c5a5e068 manifest: set revisionId as revision attribute it it is not being set in ToXml.
As we were testing superproject setting revisionId attribute to SHA and
reloading the manifest, we found out revisionId attribute is not being
saved. Made the change to save the revisionId if it is not being saved.

Tested the code with the following commands.

$ ./run_tests -v

Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I95fdf655b19648ad3e9aba10b9bed8bb9439deb6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296182
Reviewed-by: Ian Kasprzak <iankaz@google.com>
2021-02-07 17:13:35 +00:00
0286e31ec7 Update _CheckForImmutableRevision to use git rev-list
_CheckForImmutableRevision is used to see if repo can
skip fetching a project, but 'git rev-parse' with partial
clone does a data fetch to accomplish this.

Changed to use: 'git rev-list -1 --missing=allow-any <SHA>^0' which
checks the local ref without fetching from the server first.

Bug: [google internal] b/179477822

Testing:
- Unit tests
- Verified init/sync working on aosp-master
- Verified wwith a pinned manifest that local ref check works (no fetch)

Change-Id: If327b893c6658421f41df1f58c337f53b4c60ce6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/296142
Reviewed-by: Dan Willemsen <dwillemsen@google.com>
Tested-by: Ian Kasprzak <iankaz@google.com>
2021-02-05 22:00:31 +00:00
ef267722f8 sync: Added --filter=blob:none for git clone of superproject.
+ This is without --depth option. This is done for reachability.
  Server doesn't know what you know about in the history so it always
  sends you the whole thing Which is very slow.

  If we have the full history it can send you incremental update history
  which is very small and fast.

Tested the code with the following commands.

$ ./run_tests -v tests/test_git_superproject.py
$ ./run_tests -v

Tested the sync code by copying all the repo changes into my Android
AOSP checkout and doing a repo sync --use-superproject twice.

.../WORKING_DIRECTORY$ repo sync --use-superproject

Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I239de6d8f1c2ed6b4c69e7a78b8aa95338fa838c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/295362
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-02-02 16:18:06 +00:00
7caa3658b2 sync: Do a git pull with --use-superproject if superproject tree already exists.
Tested the code with the following commands.

$ ./run_tests -v tests/test_git_superproject.py
$ ./run_tests -v

Tested the sync code by copying all the repo changes into my Android
AOSP checkout and doing a repo sync --use-superproject twice.

.../WORKING_DIRECTORY$ repo sync --use-superproject

Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I7e4b1e51ca1d18b836a5fa8d139a0765262ba500
2021-02-01 12:24:51 -08:00
9e7875315f sync: Added --filter=blob:none (and no-depth) wduring git clone of superproject.
Tested the code with the following commands.

$ ./run_tests -v tests/test_git_superproject.py
$ ./run_tests -v

Tested the sync code by copying all the repo changes into my Android
AOSP checkout and doing a repo sync --use-superproject twice.

.../WORKING_DIRECTORY$ repo sync --use-superproject

Bug: https://crbug.com/gerrit/13709
Bug: https://crbug.com/gerrit/13707
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: Ieea31445ca89ba1d217e779ec7a7a2ebe81ac518
2021-02-01 20:08:00 +00:00
db3128f2ec git_command.py: Handle unicode decode error
repo diffmanifests saves git commit messages in buf and uses default
utf-8 decoding, in some scenarios git commit message can itself contain
a non UTF-8 character due to a typo or incorrect i18n.commitEncoding.

e.g.
d354d9afe923 [PATCH] fbcon: don\xb4t call set_par() in fbcon_init() if vc_mode == KD_GRAPHICS

Convert the buf containing git commits to string if decoding to utf-8
encounters an error.

Signed-off-by: Gaurav Pathak <gaurav.pathak@pantacor.com>
Change-Id: If818562f0faaa5062c765fbea11dc0e1c86a24d7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/294742
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-01-28 17:38:24 +00:00
2a2da80ba6 sync: Disable info about disabling pruning when quiet
If you have a lot of shared projects, it spams.

Bug: https://crbug.com/gerrit/13961
Change-Id: If3f5baef65930830af9a2cd01a1b593dd518ab09
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/294049
Tested-by: Anders Björklund <anders.bjorklund.2@volvocars.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-01-22 11:43:13 +00:00
6a872c9dae sync: Added --use-superproject option and support for superproject.
Added "--use-superporject" option to sync.py to fetch project SHAs from
superproject. If there are any missing projects in superprojects, it
prints the missing entries and exits. If there are no missing entries,
it will use SHAs from superproject to fetch the projects from git.

Tested the code with the following commands.

$ ./run_tests tests/test_manifest_xml.py
$ ./run_tests -v tests/test_git_superproject.py
$ ./run_tests -v

Tested the sync code by copying all the repo changes into my Android
AOSP checkout and adding <superporject> tag to default.xml. With
local modification to the code to print the status,

.../WORKING_DIRECTORY$ repo sync --use-superproject
repo: executing 'git clone' url: sso://android/platform/superproject
repo: executing 'git ls-tree'
Success: []

Bug: https://crbug.com/gerrit/13709
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: Id18665992428dd684c04b0e0b3a52f46316873a0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/293822
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-01-21 19:41:52 +00:00
df6c506a8a launcher: bump version for new release
Change-Id: I8a39630d482fc389cf497399102f795d7e576ff9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/294122
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-21 16:17:19 +00:00
febe73ff16 Update "evt" field to be logged as a string type.
Testing:
- Unit tests
- Verified git trace log has "evt": "2" (vs "evt": 2 previously)

Bug: https://crbug.com/gerrit/13966
Change-Id: I2e0c98dda0cccdd5cb6328105c11b93cd42676eb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/294123
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Ian Kasprzak <iankaz@google.com>
2021-01-19 20:35:56 +00:00
e5670c8812 launcher: add a requirements framework to declare version dependencies
Currently we don't have a way for the checked out repo version to
declare the version of tools it needs before we start running it.
For somethings, like git, it's not a big deal as it can handle all
the asserts itself.  But for things like Python, it's impossible
to reliably check before executing.

We're in this state now:
- we've been allowing Python 3.4, so the launcher accepts it
- the repo codebase starts using Python 3.6 features
- launcher tries to import us but hits syntax errors
- user is left confused and assuming new repo is broken because
  they're seeing syntax errors

This scenario is playing out with old launchers that still accept
Python 2, and will continue to play out as time goes on and we want
to require newer versions of Python 3.

Lets create a JSON file to declare all these system requirements.
That file format is extremely stable, so loading & parsing from
even ancient versions of Python shouldn't be a problem.  Then the
launcher can read these settings and check the system state before
attempting to execute any code.  If the tools are too old, it can
clearly diagnose & display information to the user as to the real
problem (and not emit tracebacks or syntax errors).

We have a couple of different tool version checks already (git,
python, ssh) and can harmonize them in a single place.

This also allows us to assert a reverse dependency if the need
ever comes up: force the user to upgrade their `repo` launcher
before we'll let them run us.  Even though the launcher warns
whenever a newer release is available, some users seem to ignore
that, or they don't use repo that often (on the scale of years),
and their upgrade jump is so dramatic that they fall back into
the syntax error pit.

Hopefully by the end of the year we can assume enough people
have upgraded their launcher such that we can delete all of the
duplicate version checks in the codebase.  But until then, we'll
keep them to maintain coverage.

Change-Id: I5c12bbffdfd0a8ce978f39aa7f4674026fe9f4f8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/293003
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-19 16:48:21 +00:00
48b2d10d8f manifest_xml: - Added doc and testing of unknown tags/elements.
Added this test to verify that older versions of repo can handle
"<superproject" element. Tested by adding "<iankaz" unknown element.

Tested the code with the following commands.

$ ./run_tests tests/test_manifest_xml.py
$ ./run_tests -v

Bug: https://crbug.com/gerrit/13709
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I858d56f38cefcfcd14474efdd631a5a940c3ce47
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/293482
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-01-12 21:49:13 +00:00
0588f3dc52 version: add remote tracking information
This tells us what --repo-rev the user is using.

Bug: https://crbug.com/1164415
Change-Id: Idb6c48e6ca5a4783c529717e6be38266bf7038b0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/293143
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-08 20:41:27 +00:00
1bb4fb222d manifest_xml: initial support for <superproject>
At most one superproject may be specified. It will be used
to specify the URL of superproject.

It would have 3 attributes: remote, name, and default.
Only "name" is required while the others have reasonable defaults.

<remote name="superproject-url" review="<url>" />
<superproject remote="superproject-url" name="platform/superproject"/>

TODO: This CL only implements the parsing logic and further work
will be in followup CLs.

Tested the code with the following commands.

$ ./run_tests tests/test_manifest_xml.py
$ ./run_tests -v

Bug: https://crbug.com/gerrit/13709
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I5b4bba02c8b59601c754cf6b5e4d07a1e16ce167
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292982
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-01-08 19:49:52 +00:00
b64bec6acc launcher: bump version for new release
Change-Id: Ie0abee81e86046f412b42f08100041cfd3689c4a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292682
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-07 22:44:53 +00:00
343d585ff9 Fix bug in git trace2 event Write() function when no config present.
See https://bugs.chromium.org/p/gerrit/issues/detail?id=13706#c9

Added additional unit tests for Write() for additional test coverage.

Testing:
- Unit tests
- Verified repo works with:
  - Valid trace2.eventtarget
  - Invalid trace2.eventtarget

Bug: https://crbug.com/gerrit/13706
Tested-by: Ian Kasprzak <iankaz@google.com>
Change-Id: I6b027cb2399bd03e453a132ad82e022a1f48476e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292762
Reviewed-by: Mike Frysinger <vapier@google.com>
2021-01-07 14:31:51 +00:00
acf63b2892 drop pyversion & is_python3 checking
We're committed to Python 3 at this point, so purge all the
is_python3 related dynamic checks.

Bug: https://crbug.com/gerrit/10418
Change-Id: I4c8b405d6de359b8b83223c9f4b9c8ffa18ea1a2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292383
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-06 18:53:58 +00:00
784ccfc040 strip python2-only coding:utf-8 & print_function settings
We're committed to Python 3 at this point, so clean up boilerplate.

Bug: https://crbug.com/gerrit/10418
Change-Id: Ib1719ba2eb65c53b94881a1a1bf203ddfcaaafed
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292382
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-06 18:53:05 +00:00
1379a9b185 launcher: add test for version requirements
Make sure the modules stay in sync in case one is updated but we
forgot to update the other.

Bug: https://crbug.com/gerrit/13795
Change-Id: I6de9533d45c083e5f7ad792ee6d541e23647de3f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292444
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-06 17:56:51 +00:00
128f34e874 main: require Python 3.5 now
We've been warning about Python 3.4 for almost a year.  This drops
support for these systems:
* Ubuntu Trusty: released Apr 2014, EOL Apr 2022
* Debian Jessie: released Apr 2015, EOL Jun 2020

So the min required distros would now be:
* Ubuntu Xenial: released Sep 2015 w/Python 3.5
* Debian Stretch: released Jun 2017 w/Python 3.6

I don't think we're quite ready to drop Python 3.5 which would affect
Ubuntu Xenial -- we'd have to update to Ubuntu Bionic from Apr 2018.
Let's see how much the community reacts to loss of Python 3.4 first.

Bug: https://crbug.com/gerrit/10418
Change-Id: Ib24a57818fdca49e23db53e1bdd1f4c76b4963f7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/291502
Reviewed-by: Chris Mcdonald <cjmcdonald@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-06 17:55:15 +00:00
30bc354e25 Enable git trace2 event format logging.
Ways to enable logging:
1) Set git's trace2.eventtarget config variable to desired logging path
2) Specify path via --git_trace2_event_log option

A unique logfile name is generated per repo execution (based on the repo session-id).

Testing:
1) Verified git config 'trace2.eventtarget' and flag enable logging.
2) Verified version/start/end events are expected format:
  https://git-scm.com/docs/api-trace2#_event_format
3) Unit tests

Bug: https://crbug.com/gerrit/13706
Change-Id: I335eba68124055321c4149979bec36ac16ef81eb
Tested-by: Ian Kasprzak <iankaz@google.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292262
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2021-01-06 17:49:41 +00:00
ce9b6c43b2 launcher: abort if python3 reexec failed
We don't support Python 2 anymore, so stop allowing it to fallback.
If we try to run the latest version with Python 2, it just hits
syntax errors which confuses people.  Dump a clear error message
that their system is too old and give up.

Bug: https://crbug.com/gerrit/13795
Change-Id: I38c243cf09502f670cddad72c2d0148f736515e0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292443
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-05 22:42:13 +00:00
47692019b3 launcher: support Python 3.5 for now
The codebase still supports Python 3.5, so allow use of that instead
of requiring Python 3.6+.  Supporting this mode well is a bit tricky
as we want to first scan for newer versions before falling back to
older ones.  And we have to avoid infinite loops in the process.

Bug: https://crbug.com/gerrit/13795
Change-Id: I47949a173899bfa9ab20d3fefa1a97bf002659f6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292442
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2021-01-05 22:18:24 +00:00
1469c28ec3 project: detach HEAD in internal worktree checkout.
When checkout is done with Git worktrees then the HEAD in the
bare-git repositories point to the initialized default (e.g.
'refs/heads/master'). This default branch does not exist
locally and is not automatically created.
When a user now creates a branch in any git repository named
'master' then it is no longer possible to get rid of this branch,
neither is it possible to switch to another branch and switch
back to this master branch. Git concludes the 'master' branch is
already checked out (in the bare Git) and that results in a
lockdown of this master branch.

To repoduce this issue, run these commands in a repo tree
checked out with --worktree:
- git checkout master # assuming the remote repo has a master branch,
                      # a local tracking branch master is created here
- git checkout -b temp
- git checkout master # This one now fails
- git branch -d master # fails too
The failure is caused by Git assuming the master branch is checked out
by the bare git repository since HEAD is pointing towards it.

To workaround this, we always detach HEAD in the bare-git when
syncing.  We don't need it to point to a ref in general, but we
would like it to be valid so git tools "just work" if they're run
in here.

Signed-off-by: Remy Bohmer <oss@bohmer.net>
Change-Id: I15c96604363c41f0d01c42f533174393097daeb5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/290985
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-12-26 07:30:40 +00:00
8add62325d Add parallelism to 'branches' command
Spread the operation of querying which local branches exist across a
pool of processes and build the name map of projects -> branches as
these tasks finish rather than blocking on the entire query. The search
operations are submitted in batches to reduce the overhead of interprocess
communication. The `chunksize` argument used to control this batch size
was selected by incrementing through powers of two until it stopped being
faster.

Change-Id: Ie3d7f799ee8e83e5058536caf53e2979175408b7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/291342
Tested-by: Chris Mcdonald <cjmcdonald@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-12-14 23:35:12 +00:00
974774761c docs: Add Markdown inline code marker around inline XML example.
Presently, this tag is not rendered --- by Gitiles, at least --- which
makes the example very confusing indeed.

Signed-off-by: Jashank Jeremy <jashank@rulingia.com.au>
Change-Id: Ia76a60d8ee0ecce8ceb32661afbd48f3b2d80fbf
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/291362
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Jashank Jeremy <jashank.jeremy@gmail.com>
2020-12-13 03:25:36 +00:00
dc60e54d36 gitc: write the manifest directly
Rather than pull the client dir out to construct the manifest
filename which the manifest itself already has, pull the filename
out and use that.

Change-Id: I33991084dcb3205f819bb841084e3c48d6ccb284
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/291264
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-12-11 18:07:15 +00:00
0a849b660f replace javadoc docs with standard python style
We don't use javadoc in this project, so clean up the few places
that slipped in with the gitc code.

Change-Id: Ia365fb2d1e3188ad16b2f65b1a3b7e8466001946
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/291262
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-12-11 17:55:38 +00:00
5e2f32fe13 init: reset hard to --repo-rev
When updating the tracking ref to whatever the user requested,
make sure we reset state completely rather than trying to update
the ref to it.  This avoids confusing git as to the current state
of the tree, and is more inline with user intentions: if they made
a local change to the checkout, but ran repo init with a specific
rev, we shouldn't stay wedged forever until they manually clean it
all up.

Bug: https://crbug.com/gerrit/12801
Change-Id: Ieba8d9c15781b4d0649bf01c7460694da63387b2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/290923
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-12-06 21:46:30 +00:00
51e39d536d manifest_xml: harmonize list fields
We allow project.groups to be whitespace or comma delimited, but
repo-hooks.enabled-list is only whitespace delimited.  This hasn't
been a big deal as it's only ever had one valid value, but if we
want to add more, we should harmonize these a bit.

Refactor the groups method to be more generic, and run the enabled-
list attribute through it.  Then add missing docs for it.

Change-Id: Iaa96a0faa9c4a68b313b49336751831b73bf855d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/290743
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-12-04 17:27:11 +00:00
6342d56914 Fix tests after "use new main branch"
Tests worked fine if init.defaultBranch main was used,
but failed due to git branch reasons if master was still used.

Since we can only use init.defaultBranch if git version >= 2.28,
I also went with a template dir HEAD main tweak if lower so tests
now pass regardless of client git default branch and version.

Test: Ran tests with ~/.gitconfig:init.defaultBranch=master
Test: Ran tests with ~/.gitconfig:init.defaultBranch=main
Test: Ran tests for both code branches of git require

Change-Id: I49fa1e4ae45b8aec16a093132ee9fa466cbc11ec
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/290404
Tested-by: Fredrik de Groot <fredrik.de.groot@volvocars.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-12-03 07:29:59 +00:00
9dfd69f773 run_tests: rewrite to use Python 3
Some distros still have `pytest` as Python 2 and sep `pytest-3`.
Rewrite this script to use `pytest-3` if available.

Change-Id: I430ed8792e7b0da9b217f948f2e983aa62bf1299
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/290503
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-12-01 19:29:47 +00:00
08eb63cea4 setup: update Python version info
Change-Id: I91056260d00215cfe9047d17664e3c3158c7bbcc
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/290502
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-12-01 19:29:43 +00:00
352c93b680 manifest: add support for groups in include
Attrib groups can now be added to manifest include, thus
all projects in an included manifest file can easily be tagged
with a group without modifying all projects in that manifest file.

Include groups will add and recurse, meaning included manifest
projects will carry all parent includes. Intentionally, no support
added for group remove, to keep complexity down.

Group handling for projects is untouched, meaning a group set on
a project will still append to whatever was or was not inherited
in parent manifest includes, resulting in union of groups inherited
and set for the project itself.

Test: manual multi-level manifest include structure, in serial and parallel,
      with different groups set on init
Test: added unit tests to cover the inheritance

Change-Id: Id2229aa6fd78d355ba598cc15c701b2ee71e5c6f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/283587
Tested-by: Fredrik de Groot <fredrik.de.groot@volvocars.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-11-26 09:13:14 +00:00
7f7acfe9fd Concentrate the RepoHook knowledge in the RepoHook class
The knowledge about running hooks and all its exception handling
is scattered over multiple files. This makes the code harder
to read, but also it requires duplication of logic in case
other RepoHooks are added to different commands.
This refactoring also creates uniform behavior of the hooks
across multiple commands and it guarantees the re-use of the same
arguments on all of them.

Signed-off-by: Remy Bohmer <github@bohmer.net>
Change-Id: Ia4d90eab429e4af00943306e89faec8db35ba29d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/277562
Tested-by: Remy Bohmer <oss@bohmer.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-11-23 09:59:16 +00:00
169b0218b3 Fix --reference option under Windows
When intializing a new repo with the --reference option on Windows 10
the objects/info/alternates in each git repository is created with
Windows line endings (\r\n), leading to the following error:

error: object directory C:/<PATH_TO_MIRROR>/<REPO_NAME>.git/objects?
does not exist; check .git/objects/info/alternates

This can be fixed by simply using unix line endings on both
Windows and unix platforms.

Reported-by: Francisco Javier Alvarez Garcia <javier.alvarez.garcia.17@gmail.com>
Follow-up-from: I268fe029ede68802c21037b0f2ae8a95afb85e48
Bug: https://crbug.com/gerrit/13208
Change-Id: I6da60c4ca957778b3c42ab6b9ad85c40483f0042
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/289431
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Remy Bohmer <oss@bohmer.net>
2020-11-23 09:17:32 +00:00
44bc9643ed Always use Unix EOL for worktree .git and gitdir files
Worktree .git and gitdir reference files are written by Git with
Unix line ending, even on Windows & macOS. The conversion to
relative paths makes these files end with DOS line endings in
Windows.  The Git integration in Visual Studio 2019 cannot deal
with these DOS line endings and considers these worktrees invalid.

Signed-off-by: Remy Bohmer <github@bohmer.net>
Change-Id: I088cfd994f3cc31db4e0ca7791fa0a4ee3ac222f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/289310
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Remy Bohmer <linux@bohmer.net>
2020-11-20 20:53:43 +00:00
d7f8683daf project: do not update local published/ refs in dryrun mode
Bug: https://crbug.com/gerrit/13087
Change-Id: I197e6d6d07c7d325ac294b597d42e895f77c737f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/289182
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-11-20 04:08:19 +00:00
8c1e9cbef1 manifest_xml: refactor manifest parsing from client management
We conflate the manifest & parsing logic with the management of the
repo client checkout in a single class.  This makes testing just one
part (the manifest parsing) hard as it requires a full checkout too.

Start splitting the two apart into separate classes to make it easy
to reason about & test.

Change-Id: Iaf897c93db9c724baba6044bfe7a589c024523b2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/288682
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-11-18 19:10:57 +00:00
a488af5ea5 main: require Python 3 now
We've been warning about this for more than 6 months (with public
announcements even older).  Lets make it a failure now to see who
hasn't upgraded yet.

Bug: https://crbug.com/gerrit/10418
Change-Id: Iec3e2cbf87de434021921616683d360bc4fef77a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/280796
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-11-17 15:04:20 +00:00
e283b95cf2 tests: use new main branch
Now that we clone "main" by default, use that for our local test.

Change-Id: Ib8420074bdfabfcb9d5252a3a0ecd3d852ca36e8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/288422
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-11-17 04:29:09 +00:00
dc5c4d1d11 sync: respect --force-sync when fetching manifest project updates
The --force-sync option was being passed down for all updates except
for the manifest project, so add that there too.

Bug: https://crbug.com/gerrit/11034
Change-Id: I33818b652f828c6b847dbc70f1fedfac5ac17bbe
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/228146
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-11-17 03:06:06 +00:00
23411d3f9c manifest: add a --json output option
Sometimes parsing JSON is easier than parsing XML, especially when
the XML format is limited (which ours is).  Add a --json option to
the manifest command to quickly emit that form.

Bug: https://crbug.com/gerrit/11743
Change-Id: Ia2bb254a78ae2b70a851638b4545fcafe8c1a76b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/280436
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-11-17 01:38:00 +00:00
160748f828 upload: improve tip for fixing upload remote
Instead of assuming the repo client is tracking the "master" branch
of the manifest repo, use the existing info we have to display the
right info to the user.

Bug: https://crbug.com/gerrit/13339
Change-Id: I8b265f4b2e075fdc41909b1f3dff9aee87384353
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/287279
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-11-16 23:13:02 +00:00
6e89c965f4 switch to "main" branch for development
We're migrating from "master" to "main" as the default development
branch.  This only affects repo itself, not manifests.

Change-Id: I27489dd721c9a467a1c43736808cb3b3c1365433
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/288082
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-11-16 05:07:33 +00:00
1f20776dbb manifest_xml: correct project revisionId for extend-project
Using sha1 manifest, project's revisionId is initialized
first by the manifest.
An update of a projet revision by extend-project node does
not apply to the revisionId which is therefore kept to the
initial value.

Resets revisionId value when revision is updated by an
extend-project node.

Change-Id: I873af283890cebaeaabde966f04b125642af929f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/275715
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Miguel Gaio <miguel.gaio@renault.com>
2020-11-12 09:00:08 +00:00
16c1328fec Move RepoHook class from project.py file to dedicated file
The project.py file is huge and contains multiple
classes. By moving it to seperate class files the code
becomes more readable and maintainable.

Signed-off-by: Remy Bohmer <github@bohmer.net>
Change-Id: Ida9d99d31751d627ae1ea0373418080696d2e14b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/281293
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Remy Bohmer <linux@bohmer.net>
2020-11-03 22:08:08 +00:00
6248e0fd1d launcher: simplify .repo search ceiling check
In the .repo discovery loop

  while curdir != '/' and curdir != olddir:
    ... break if we found .repo ...
    olddir = curdir
    curdir = os.path.dirname(curdir)

the "while" condition is meant to avoid searching forever if we do not
find .repo before reaching the top-level directory of the filesystem.
For that purpose, the first half of the condition is redundant; once
we reach "/", the parent directory will be "/" again and the curdir !=
olddir check would suffice to terminate the search.  Simplify by
removing the redundant first half of the check.

Noticed by code inspection.  The first half of the check was retained
when introducing the second half in df14a70c ("Make path references OS
independent", 2011-01-09), in an excess of caution.

This also improves consistency a little: if I start with curdir =
'/home/me', then with the redundant check in place we search

	/home/me
	/home

before hitting / and giving up.  On Windows, if I start with
'c:/users/me', then we search

	c:/users/me
	c:/users
	c:/

before hitting a repetition and giving up.  Fortunately it is not
common for people to set up repo clients at the top level of
filesystems, but consistently following the latter behavior should
make debugging a little easier in case it comes up.

Link: https://gerrit-review.googlesource.com/id/Ib9e830e3b9adfb1c4e56f3bcfba4746c401fb84f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/286002
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Jonathan Nieder <jrn@google.com>
2020-11-03 20:27:19 +00:00
50a81de2bc init: use the remote default manifest branch
Instead of hardcoding "master" as our default, use the remote server's
default branch instead.  For most people, this should be the same as
"master" already.  For projects moving to "main", it means we'll use
the new name automatically rather than forcing people to use -b main.

For repositories that never set up a default HEAD, we should still use
the historical "master" default.

Bug: https://crbug.com/gerrit/13339
Change-Id: I4117c81a760c9495f98dbb1111a3e6c127f45eba
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/280799
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-09-09 05:46:07 +00:00
0501b29e7a status: Use multiprocessing for repo status -j<num> instead of threading
This change increases the speed of the command with parallelization with
processes.  The parallelization with threads doesn't work well, and
increasing the number of jobs to many (8 threads ~) didn't increase the speed.
Possibly, the global interpreter lock of Python affects.

Bug: https://crbug.com/gerrit/12389
Change-Id: Icbe5df8ba037dd91422b96f4e43708068d7be924
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/279936
Tested-by: Kimiyuki Onaka <kimiyuki@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-09-09 03:52:24 +00:00
4e1fc1013c manifest: drop support for local_manifest.xml
We deprecated this 8 years ago.  Time to drop it to simplify the code
as it'll help with refactoring in this module to not migrate it.

Change-Id: I2deae5496d1f66a4491408fcdc95cd527062f8b6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/280798
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
2020-09-08 17:00:06 +00:00
4b325813fc stop testing Python 2.7
A recent change broke `repo version` on Python 2.7.  Rather than
fix it, lets drop Python 2.7 support so it can slowly rot.

Bug: https://crbug.com/gerrit/10418
Change-Id: I5c6e3d18e4a193b0a978062c23f7cea392e95d0f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259155
Reviewed-by: David Pursehouse <david.pursehouse@gmail.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-09-06 17:53:47 +00:00
0578ebf61a init: reject unknown args
If you pass args to `repo init` when first creating a checkout, the
repo launcher throws an error.  But the init subcommand that runs in
an existing checkout silently ignores them.  Throw a proper error.

Change-Id: I433bfcc73902d25f6b6a2974e77f6a977a75ed16
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/279696
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-09-02 07:53:16 +00:00
65f51ad29b Fix Git base version for worktreeconfig extension
worktreeconfig extension only appears with version Git 2.20.0

Change-Id: I3ea8b7d9f8a1f7953e536edd77b09cbc4f8f3158
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/276700
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Adrien Bioteau <adrien.bioteau@gmail.com>
2020-07-30 20:46:11 +00:00
80944b538d upload: exit non-zero when preupload hooks fail
Bug: https://crbug.com/gerrit/13159
Change-Id: Id140b619242c841223c6bc5d4aa0c37a7ce0219d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/276294
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-07-25 08:31:52 +00:00
89f3ae5ae6 release-process: document schedule (including freezes) publicly
Change-Id: Ic037b54630017740d7859292b32b8c57f4af7854
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/274772
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-07-23 08:07:38 +00:00
ac29ac397f subcmds/sync.py: Fix typo in help
Change-Id: I70b63477241284249e395b8b0a220cb6f44f836b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/270183
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@digital.ai>
Tested-by: David Pursehouse <dpursehouse@digital.ai>
2020-06-06 23:46:00 +00:00
cebf227026 manifest: normalize name & path when constructing fs paths
If the manifest uses a trailing slash on the name attribute, repo
will construct bad internal filesystem paths which confuses tools
later on.

For example, this manifest entry:
  <project name="aosp/platform/system/libhidl/" ...
will cause repo to use paths like:
  .repo/project-objects/aosp/platform/system/libhidl/.git/
when it really should be using:
  .repo/project-objects/aosp/platform/system/libhidl.git

Apply the normalization when we construct the local filesystem paths
as we cannot guarantee that the remote URL constructed from these
will behave the same.  A server might really want:
  https://example.com/aosp/platform/system/libhidl/
and would throw an error if we instead tried to fetch:
  https://example.com/aosp/platform/system/libhidl

Unfortunately, any existing repo client checkouts that use such a
manifest will hit a one-time sync error as the internal git location
has changed.  I'm not sure there's a way to cleanly migrate that.

Bug: https://crbug.com/1086043
Change-Id: I30bea0ffd23e478de89a035f408055e48a102658
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/268742
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@digital.ai>
2020-05-26 05:15:58 +00:00
7ae210a15b sync: fix duplicate word in description
Bug: https://crbug.com/gerrit/12814
Change-Id: Id722eec9a59dded588f13bc605ce2c94b4047265
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/268739
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@digital.ai>
2020-05-24 23:51:28 +00:00
60fc51bb1d launcher: fix version to latest
We've already released 2.7, and the next tag is 2.8, so this should
be pulled up to 2.8 so it'll stay in sync.

Change-Id: Id47bdbdb8050b29ea36442ac2149dd948648237f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/268572
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@digital.ai>
2020-05-21 22:46:11 +00:00
72325c5f3e launcher: bump version for cli changes
Change-Id: I9b2194df0c1af3bc5b42115a25992747368a7383
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/268532
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: Xin Li <delphij@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-05-21 21:04:42 +00:00
d79a4bc51b Make partial clone imply no-clone-bundle by default.
For large projects, clone bundle is useful because it provided a way to
efficiently transfer a large portion of git objects through CDN, without
needing to interact with git server. However, with partial clones, the
intention is to not download most of the objects, so the use of clone
bundles would defeat the space savings normally seen with partial
clones, as they are downloaded before the first fetch.

A new option, --clone-bundle is added to override this behavior.
Add a new repo.clonebundle variable which remembers the choice if
explicitly given from command line at repo init.

Change-Id: I03638474af303a82af34579e16cd4700690b5f43
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/268452
Tested-by: Xin Li <delphij@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-05-21 19:47:36 +00:00
682f0b6426 Fix how we format the full destination branch when uploading.
If the dest-branch attribute is set in the project manifest, then
we need to push to that branch.  Previously, we would unconditionally
pre-pend the refs/heads prefix to it.  The dest-branch attribute is
allowed to be a ref expression though, so it may already have it.

Simple fix is to check if it already has the prefix before adding it.

Bug: crbug.com/gerrit/12770

Change-Id: I45d6107ed6cf305cf223023b0ddad4278f7f4146
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/268152
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Sean McAllister <smcallis@google.com>
2020-05-19 15:25:42 +00:00
e7082ccb54 repo info findRemoteLocalDiff use short branch
When running repo info -d an error would be thrown saying:
  fatal: bad revision 'refs/remotes/m/refs/heads/master..'

Using the short branch name here instead, like 'refs/remotes/m/master..'
resolves this issue.

Signed-off-by: Daniel Kutik <daniel.kutik@lavawerk.com>
Change-Id: I50ea92c45c011b2c3e3a63803decb88e7837a380
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/266578
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-05-12 16:15:01 +00:00
dbfbcb14c1 project.py: Fix check for wild cards
The intention of the check is to verify whether the target
file name contains a wild card. The code, however, assumes
that if the file is non-existent - it contains a wild card.
This has the side effect that a target file that does not
exist at the moment of the check is considered to contain a
wild card, this leads itself to softlink not being created.

Change-Id: I4e4cd7b5e1b8ce2e4b2edc9abf5a1147cd86242f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/265736
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Angel Petkov <apetkov86@gmail.com>
2020-05-05 17:53:11 +00:00
d0ca0f6814 Parse included files when reading git config files
Git config files may have an include tag pointing to another file.
The included file is not parsed unless “git config --list” is
explicitly told to follow includes by adding the argument ”--includes”.

This change add the "--includes" when parsing the global gitconfig file.

Change-Id: I892c9a3a748754c1eb8c9e220578305ca5850dd5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/264759
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Ulrik Laurén <ulrik.lauren@gmail.com>
2020-04-29 18:28:41 +00:00
433977e958 repo: exit on missing entry point
exit if no repo_main can be found right before executing the command.
This happens for instance when 'repo init' is run on root path
(for example in a container). Without this counter measure the tool
will crash at exec_command with
TypeError: sequence item 1: expected str instance, NoneType found

Change-Id: Ia8480cfe2151c3b35c9572789ad8cb619288cce1
Signed-off-by: Konrad Weihmann <kweihmann@outlook.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/263457
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@digital.ai>
2020-04-28 17:02:46 +00:00
dd37fb2222 main: re-exec self with the current interp
The launcher already raised itself up to use Python 3 on the fly, and
the main.py script uses a plain `python` shebang.  So make sure we use
the active interpreter when re-execing ourselves to avoid falling back
down to Python 2 (which then triggers warnings).

Change-Id: Ic53c07dead3bc9233e4089a0a422f83bb5ac2f91
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/263272
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@digital.ai>
2020-04-28 02:54:50 +00:00
af908cb543 When writing manifest, set the dest-branch attribute for projects
When generating a revision locked manifest, we need to know what
ref to push changes to when doing 'repo upload'.  This information
is lost when we lock the revision attribute to a particular commit
hash, so we need to expose it through the dest-branch attribute.

Bug: https://crbug.com/1005103
Test: manual execution
Change-Id: Ib31fd77ad8c9379759c4181dac1ea97de43eec35
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/263572
Tested-by: Sean McAllister <smcallis@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-04-20 16:35:02 +00:00
74e8ed4bde Expose upstream and dest-branch attributes through environment
Recent changes in ChromeOS Infra to ensure we're reading from
snapshot manifests properly have exposed several bugs in our
assumptions about manifest files.  Mainly that the revision field
for a project does _not_ have to refer to a ref, it can just be
a commit hash.

Several places assume that the revision field can be parsed as a
ref to get the branch the project is on, which isn't true.  To fix
this we need to be able to look at the upstream and dest-branch
attributes of the repo, so we expose them through the environment
variables set in `repo forall`.

Test: manual 'repo forall' run
Bug: https://crbug.com/1032441

Change-Id: I2c039e0f4b2e0f430602932e91b782edb6f9b1ed
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/263132
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Sean McAllister <smcallis@google.com>
2020-04-16 18:42:53 +00:00
2fe84e17b9 project.py: Remove extraneous ','
Bug: https://crbug.com/1061473
Change-Id: I0f02f122d6313679c1ae5ad6fb4e05f68b764186
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/263112
Tested-by: George Engelbrecht <engeg@google.com>
Reviewed-by: George Engelbrecht <engeg@google.com>
Reviewed-by: SPA SARC <spanc.sarc@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-04-15 19:55:44 +00:00
1122353683 Revert "commit-msg: Insert Change-Id at start of trailers"
This reverts commit 653f8b711b.

Reason for revert: This requires git-2.15 which is much newer than
repo itself requires.  Lets pull it until we can figure out something
on the Gerrit side.

Bug: https://crbug.com/gerrit/12546
Change-Id: I5148f8a9cab5f0c305c020e31627b4af88cd5c95
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/263012
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-04-15 07:17:16 +00:00
b6871899be project: have clone.bundle failures print better diagnostics
Bug: https://crbug.com/1061473

Change-Id: If066dc56ca575720bfb25c1a9892dbd6f4af15c6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/261852
Tested-by: George Engelbrecht <engeg@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-04-15 06:52:52 +00:00
8e0fe1920e Use hash for ControlPath instead of full variables
The generated socket path can be too long, if your FQDN is very long...

Typical error message from ssh client:
unix_listener: path "/tmp/ssh-fqduawon/master-USER@HOST:PORT.qfCZ51OAZgTzVLbg" too long for Unix domain socket

Use a hashed version instead, to keep within the socket file path limit.

This requires OpenSSH_6.7p1, or later.

Change-Id: Ia4bb9ae8aac6c4ee31d5a458f917f3753f40001b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255632
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Anders Björklund <anders.bjorklund.2@volvocars.com>
2020-04-15 06:51:22 +00:00
d086467012 forall.py: Close file after removing the stream
In order to remove the stream fileno() will be called on the filedescriptor.
If the file is already closed fileno() will raise an error and forall
will fail.

Bug: https://crbug.com/gerrit/12563
Change-Id: Ib7b895fe881c844e3eb3672b011fdcdbdae63024
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/262838
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Karsten Pfeiffer-Raddatz <raddatz.karsten@gmail.com>
2020-04-14 06:49:31 +00:00
2735bfc5ff tests: fix SetupGnuPG test
The SetupGnuPG test tries to test the full setup, including the
creation of the directories. In order to do that, it create a
temporary directory, and redefines the home_dot_repo to point there.

When a home_dot_repo directory does not exist, it should be created.
The gpg_dir, which should exist inside home_dot_repo, also needs to be
created if it does not exist. However, since the gpg_dir path is
relative to home_dot_repo, once we redefine one, we need to redifine
the other.

The failure of this test might have gone unnoticed so far, since in
only fails if you do not have a ~/.repoconfig/gnupg/ on the
environment you are running the tests on.

Change-Id: Ic69d59e56137eea43349a61b5cf81f215c6a7f9a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/262573
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Marcos Marado <mindboosternoori@gmail.com>
2020-04-12 17:12:14 +00:00
653f8b711b commit-msg: Insert Change-Id at start of trailers
In older versions of Gerrit the Change-Id field was inserted at the
start of the trailers. Commit 68296f71804feab2e0ae18ae33f834a8a41621e4
simplified the trailers code by using git trailers instead of custom
code but now inserts Change-Id at the end of the trailers section.

A consequence of this is that folks who sign-off their commits using
`git commit -s` now has the sign-off appear first followed by
Change-Id. If the user then runs `git commit -s --amend` to update
the change because the Sign-off-by line is not last, git inserts
a 2nd duplicate Signed-off-by line.

This patch simply restores the previous behaviour of the Gerrit
commit-msg hook where Change-Id would be inserted before the
Sign-off-by line to avoid this issue.

Backported from [1] by Thanh Ha.

[1] https://gerrit-review.googlesource.com/c/gerrit/+/262072

Bug: https://crbug.com/12546
Change-Id: I1406c763a3935761247f6771f55e02367f698e6e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/262352
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-04-08 01:47:54 +00:00
9bc283e49b sync: add retry to fetch operations
Add retries with exponential backoff and jitter to the fetch
operations. By default don't change behavior and enable
behind the new flag '--fetch-retries'.

Bug: https://crbug.com/1061473

Change-Id: I492710843985d00f81cbe3402dc56f2d21a45b35
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/261576
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: George Engelbrecht <engeg@google.com>
2020-04-02 21:17:54 +00:00
b4a6f6d798 version: include tag commit date for easy reference
This is more for users trying to get a sense of how old/new their
current version of repo is when debugging issues.

Change-Id: Ifb413c679bb8c8dbf4f9334137adf086bb000a68
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/261192
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-03-31 03:27:57 +00:00
3e5b269fc6 launcher: bump version for accumulated fixes
Change-Id: I45da9facb525355c4963735e087d87024dea2017
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/260232
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-03-25 05:54:26 +00:00
cdb344c0e7 launcher: avoid crash when executing out of checkout
When developing repo itself, it helps to run repo directly out of it
and to run bisection tools.  The current _SetDefaultsTo logic fails
in that situation though as it wants a branch, but the source isn't
checked out to one.  Now that we support tracking commits via the
--repo-rev setting, fall back to using the current HEAD commit.

Change-Id: I37d79fd9f7bea87d212421ebed6c8267ec95145f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/260192
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-03-25 04:56:16 +00:00
e257d56665 version: fix running under Python 2
This gets the unittests passing again for now.

Change-Id: Ibed430a305bc26b907ad0ea424c7eec7de37e942
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259994
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-03-25 04:56:07 +00:00
3599cc3975 init: respect --repo-rev changes
We respect this option when running the first `repo init`, but then
silently ignore it once the initial sync is done.  Make sure users
are able to change things on the fly.

We refactor the wrapper API to allow reuse between the two init's.

Bug: https://crbug.com/gerrit/11045
Change-Id: Icb89a8cddca32f39a760a6283152457810b2392d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/260032
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-03-25 04:55:50 +00:00
cfc8111f5e init: allow REPO_REV/--repo-rev to specify commits/tags
While the help/usage suggested that revisions would work, they never
actually did, and just throw confusing errors.  Now that we warn if
the checkout isn't tracking a branch, allow people to specify commits
or tags explicitly.  Hopefully our nags will be sufficient to keep
most people on the right path.

Bug: https://crbug.com/gerrit/11045
Change-Id: I6ea32c677912185f55ab20faaa23c6c0a4c483b3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259492
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-03-24 05:01:23 +00:00
587f162033 tests: add more wrapper unittests
Change-Id: Ic6b4eb96b871793bc9463c9047674cf3cfbe4b5e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259993
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-03-24 03:08:25 +00:00
78964472ad download: add a --branch option
This allows people to quickly create new branches when pulling down
changes rather than having to juggle the git calls themselves.

Bug: https://crbug.com/gerrit/11609
Change-Id: Ie6a4d05e9f4e9347fe7f7119c768e6446563ae65
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259855
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-03-23 00:31:10 +00:00
05097c6222 download: unify error handling with sub git calls
We gracefully handle cherry-pick errors, but none of the others
which means people get confusing Python tracebacks.  Move the
main logic in a single GitError try block so we can show pretty
error messages for all of them.

Change-Id: I52cdf6468d21a98de7f65b86d5267b3caabd5af8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259854
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-03-23 00:28:54 +00:00
915fda130e download: support -x when cherry-picking
This is a pretty common option for people to want too use, so include
it as a pass-thru option when cherry-picking.

Bug: https://crbug.com/gerrit/9418
Change-Id: I2a24c1ed7544541719caa4d3c0574347a151a1b0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259853
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-03-23 00:27:52 +00:00
ea43176de0 download: support --ff when cherry-picking
The git cherry-pick already supports this, so plumb the existing repo
option down.  Otherwise it's confusing when people use -c --ff and it
doesn't use that behavior.

Change-Id: Id68932ffa09204bb30b92a21aff185c00394a520
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259852
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-03-23 00:26:26 +00:00
58ac1678e8 init: rename --repo-branch to --repo-rev
We refer to this as "revision" in help text, and in REPO_REV envvar,
so rename to --repo-rev to be consistent.  We keep --repo-branch for
backwards compatibility, but as a hidden option.

Bug: https://crbug.com/gerrit/11045
Change-Id: I1ecc282fba32917ed78a63850360c08469db849a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259352
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-18 00:24:43 +00:00
e1111f5710 launcher: init: stop passing --repo-url/--repo-branch down
When the launcher handles the init subcommand, it takes care of
setting the repo url & branch itself when cloning.  So we don't
need to pass them down to the checked out init subcommand.

Further, the init subcommand has never actually done anything
with those options, so there's no point in passing them.

We'll be changing the latter behavior so that init will reset
the url/branch when specified with an existing repo checkout
which means passing them through adds overhead: the launcher
will checkout to the right value, then chain to the sub-init
which will then reset the checkout to the same value.

Bug: https://crbug.com/gerrit/11045
Change-Id: Ia2a4ab9d86febc470aea4abd73d75bb10e848b56
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259312
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-17 09:24:04 +00:00
7936ce8677 init: respect --repo-url changes
We respect this option when running the first `repo init`, but then
silently ignore it once the initial sync is done.  Make sure users
are able to change things on the fly.

Bug: https://crbug.com/gerrit/11045
Change-Id: I129ec5fec43a85067d555bb60c0d1ae02465f139
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/258893
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-17 05:39:17 +00:00
23c900f105 sync: warn if not tracking a branch
Since tracking a branch prevents repo from updating, make sure we
warn people about the situation when using `repo sync`.

Bug: https://crbug.com/gerrit/11045
Change-Id: I966513f510827cc93194f8df176c6745946bd739
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/258892
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-17 05:38:19 +00:00
bb930461ce subcmds: stop instantiating at import time
The current subcmds design has singletons in all_commands.  This isn't
exactly unusual, but the fact that our main & help subcommand will then
attach members to the classes before invoking them is.  This makes it
hard to keep track of what members a command has access to, and the two
code paths (main & help) attach different members depending on what APIs
they then invoke.

Lets pull this back a step by storing classes in all_commands and leave
the instantiation step to when they're used.  This doesn't fully clean
up the confusion, but gets us closer.

Change-Id: I6a768ff97fe541e6f3228358dba04ed66c4b070a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259154
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-17 00:08:52 +00:00
d3639c53d5 subcmds: centralize all_commands logic
The branch->branches alias is setup in the main module when that
really belongs in the existing all_commands setup.

For help, rather than monkey patching all_commands to the class,
switch it to use the state directly from the module.  This makes
it a bit more obvious where it's coming from rather than this one
subcommand having a |commands| member added externally to it.

Change-Id: I0200def09bf4774cad8012af0f4ae60ea3089dc0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259153
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-17 00:08:52 +00:00
f725e548db upload: add config setting for upload notifications
This allows people to set default e-mail notifications via gitconfig.

Bug: https://crbug.com/gerrit/12451
Change-Id: Ic04ea3b7df0c5603c491961112c5be8cabb9dddd
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/259014
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-15 08:31:35 +00:00
4847e05743 repo/init/sync: rework default git download output
When we download git sources, we get a progress bar (good) and we get
a dump of all the refs we downloaded (bad) as it can easily be 100+ if
not 1000+ depending on the project (for each git repo!).  Lets rework
the output behavior so that:
* quiet: Only errors.
* default: Progress bars (if on a tty).
* verbose: Full output (progress bars & downloaded refs).

Bug: https://crbug.com/gerrit/11293
Change-Id: I87a380075e79de6805f91095876dd1b37d32873a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256456
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
2020-03-14 04:02:42 +00:00
bb8ee7f54a manifest_xml: unify bool & int parsing
We've been overly lenient with boolean parsing by ignoring invalid
values as "false" even if the user didn't intend that.  Turn all
unknown values into warnings to avoid breaking existing manifests,
and unify the parsing logic in a helper to simplify.

We've been stricter about numbers, but still copying & pasting
inconsistent code.  Add a helper for this too.  For out of range
sync-j numbers (i.e. less than 1), throw a warning for now, but
mark it for future hard failures.

Change-Id: I924162b8036e6a5f1e31b6ebb24b6a26ed63712d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256457
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-03-13 18:48:52 +00:00
23d7dafd10 Reland "Port _FileDescriptorStreamsNonBlocking to use poll()"
Now that repo 2 requires Python 3, we can reland this.

This reverts commit 91d9587e45.

Change-Id: Id5b178ebb53bdba04bfa79cbb5c698ae5080c957
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/258672
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Theodore Dubois <tbodt@google.com>
2020-03-13 17:45:36 +00:00
8b40c00eab diffmanifests: honour --pretty-format when printing --raw
Enable using --pretty-format to build a custom subject line
even when using the --raw option.

Change-Id: I0c1e682d984e56698fe65939aa6de12a653cd0f1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/258565
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Connor Newton <connor@ifthenelse.io>
2020-03-13 09:50:46 +00:00
e20da3eeed sync: fix os.environ logic errors
This is a dict to index, not a function to call.

Change-Id: I0117eeaaa8b2ef4762ab6f0d22f9ffdaee961f52
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/258132
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-07 13:10:14 +00:00
910dfe8497 launcher: warn when verification is disabled
Make it clear(er) to the user that this option is dangerous.

Bug: https://crbug.com/gerrit/11045
Change-Id: I5580996c26653a7c823b69008de3626abf1b0068
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/257333
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-03-01 03:44:03 +00:00
21b7fbe14d project: fix m/ pseudo ref handling with git worktrees
Since most ref namespaces are shared among all worktrees, trying to
set the pseudo m/<branch> in the common git repo ends up clobbering
each other when using shared checkouts.  For example, in CrOS:
  <project path="src/third_party/kernel/v3.8"
           name="chromiumos/third_party/kernel"
           revision="refs/heads/chromeos-3.8" />
  <project path="src/third_party/kernel/v3.10"
           name="chromiumos/third_party/kernel"
           revision="refs/heads/chromeos-3.10" />

Trying to set m/master in chromiumos/third_party/kernel.git/ will
keep clobbering the other.

Instead, when using git worktrees, lets set the m/ pseudo ref to
point into the refs/worktree/ namespace which is unique to each
git worktree.  So we have in the common dir:
  chromiumos/third_party/kernel.git/:
    refs/remotes/m/master:
      ref: refs/worktree/m/master
And then in each worktree we point refs/worktree/m/master to the
respective manifest revision expression.  Now people can use the
m/master in each git worktree and have it resolve to the right
commit for that worktree.

Bug: https://crbug.com/gerrit/12404
Change-Id: I78814bdd5dd67bb13218c4c6ccd64f8a15dd0a52
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256952
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-29 07:22:08 +00:00
b967f5c17a release: add tips for when to push prod changes
Change-Id: Iabfdd322acbc60ee16e5222ecdb261cd3a3c2cf9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/257332
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-29 07:20:24 +00:00
dc15532bee README: use new bug template
This will prefill all the settings so users can report things better.

Change-Id: I1ccfd3a2c6835489db1fd2ba71aee39058ffe597
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256872
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-26 23:20:43 +00:00
eea23b44a9 main: improve launcher update messaging wrt system installs
Some users get repo from their distro (e.g. /usr/bin/repo), so the
suggestion to copy over top of it makes people uneasy, if it's even
possible in the first place.

Bug: https://crbug.com/gerrit/12335
Change-Id: I9a0c83d6ba0f466fa8e6d61f674ee13396f9a968
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256893
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-26 23:20:10 +00:00
5f11eac147 launcher/version: include OS/CPU info in output
We often ask users what OS/version they're running when debugging.
Include that in the version output to simplify the process.

Change-Id: Ie480b6d1c874e6f4c6f4996a03795077b844f858
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256732
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-25 23:31:47 +00:00
b0fbc7fb58 upload: drop support for drafts
Draft CLs were replaced by private/wip CLs in Gerrit instead years ago.

Change-Id: If4f3d6606aad40a6f1617a49681dfd45c64d3d37
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256673
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-25 20:58:09 +00:00
4c418bf423 README: link to mailing list & add Contact section
Change-Id: I65834e74c1c74f257d17b9da84b00e855ad42599
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256464
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-24 23:36:21 +00:00
fc1b18ae9e upload: allow users to set labels when uploading
Bug: https://crbug.com/gerrit/11801
Change-Id: I060465105b4e68ddfc815e572f62bf5dac2c1ffd
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256614
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-24 23:35:47 +00:00
d957ec6a83 manifest_xml: skip config lookup during first init
Trying to use the config state when the git tree hasn't yet been
created hits bad side effects.  Add a check to avoid probing the
config logic during the first run.  It's not clear what's going
wrong at the lower layers, but this gets us back to the behavior
before we added worktree support, so lets settle the status quo.

Bug: https://crbug.com/gerrit/12387
Change-Id: I85b56797455f3c2e249d02c18496e060be05501d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256592
Reviewed-by: Xin Li <delphij@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-24 21:17:08 +00:00
9f91c4395a project: replace GetHeadPath with new git helper
Change-Id: I79931cb484508c78f6a8b8413d05b85ed8bc6d98
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256533
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-24 17:41:40 +00:00
4b0eb5a441 project: fix rebase check with worktrees
Add a helper to our git wrapper to find the .git subdir,
and then use that to detect internal rebase state.

Change-Id: I3b3b6ed4c1f45cc8c3c98dc19c7ca3aabdc46905
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256532
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-24 17:41:36 +00:00
d38300c756 manifest: support optional --manifest-name
Still use the repo manifest by default as before, but gives us
the option of overriding it to support e.g.: using a subset of
the full manifest.

Change-Id: Ia42cd1cb3a0a58929d31bb01c9724e9d31f68730
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256372
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Sean McAllister <smcallis@google.com>
2020-02-22 19:17:40 +00:00
dcbfadf814 repo/init: improve basic progress messages
We produce some simple "Get" messages that aren't super clear as to
what they're doing, especially for people not familiar with repo.
Rephrase these to explicitly state the thing we're doing so it's
clear why we're downloading a particular source.

Bug: https://crbug.com/gerrit/11293
Change-Id: I0749504f17c5385c6c65274a274e0ae25b117413
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256455
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-22 08:23:51 +00:00
edd3d45b35 repo/init: add --verbose flags
We don't use these for much yet, but init passes it down to the
project sync layers which already has support for verbose mode.

Change-Id: I651794f1b300be1688eeccf3941ba92c776812b5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256454
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-02-22 06:31:22 +00:00
71928c19a6 repo: show redirects when tracing commands
This copies the output style we use in git_command for showing output
and input redirections.

Change-Id: I449b27e7b262e1b30b24333109a1d91d9c7b1ce7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256453
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-22 05:56:06 +00:00
f5dbd2eb07 docs: update Windows info
Add a section on worktrees to avoid symlink problems, and
note that Python 3 is now a hard requirement.

Change-Id: I83811db88692127c40cec8270f6f9486c639dc3f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256314
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-22 04:56:24 +00:00
0b888912cb init: hide summary output when using --quiet
Change-Id: I5e30a6d6a1c95fb8d75d8b0f4d63b497e9aac526
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256452
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-22 04:54:28 +00:00
75264789c0 project: fix worktree init under Windows
Git likes to create .git files with read-only permissions which makes
it difficult to open+truncate+write in situ under Windows.  Delete it
before we write the file content to workaround.

Change-Id: I3effd96525f0dfe0b90e298b6bf0b856ea26aa03
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256412
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-22 04:39:55 +00:00
a269b1cb9d manifest_xml: change .repo/manifest.xml to a plain file
Changing this to a file instead of using a symlink serves two purposes:
* We can insert some comments & doc links to help users learn what this
  is for, discover relevant documentation, and to discourage them from
  modifying things.
* Windows requires Administrator access to use symlinks.  With this
  last change, Windows users can get repo client checkouts with the new
  --worktree option and not need symlinks anywhere at all.  Which means
  they no longer need to be an Administrator in order to `repo sync`.

Change-Id: I9bc46824fd8d4b0f446ba84bd764994ca1e597e2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256313
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-22 04:38:17 +00:00
7951e14385 project: fallback to hardlinks with git hooks
Windows requires Administrator access to create symlinks.  We can
mitigate this a bit by falling back to hardlinks as those may be
created by any user on the system.  Do this with the git hooks as
these are supposed to be internal only and people shouldn't be
modifying them.  If they do, they'll have to delink first.  This
seems worth it to allow repo usage without extra privileges.

Change-Id: I996ea9c9238f7bd7d27d1d9b1f2786593bf75ef7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256312
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-21 23:46:54 +00:00
8c268c0e7b release: import some helper scripts for managing official releases
Change-Id: I9abebfef5ad19f6a637bc3b12effea9dd6d0269d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256234
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-21 05:20:58 +00:00
d9254599f9 manifest/tests: get them passing under Windows
We also need to check more things in the manifest/project handlers,
and use platform_utils in a few places to address Windows behavior.

Drop Python 2.7 from Windows testing as it definitely doesn't work
and we won't be fixing it.

Change-Id: I83d00ee9f1612312bb3f7147cb9535fc61268245
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256113
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-21 05:17:05 +00:00
746e7f664e project: unify StartBranch flows behind git-update-ref
We're using this for git worktrees because it handles the .git file
format, but it should work for all flows.  Unify to simplify.  This
also fixes the worktree logic which duplicated .git/config settings.

Change-Id: Ie3af2e206710859dccfc376b3593f415d6830738
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256034
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-02-21 05:12:47 +00:00
f241f8c094 repo: fix up license text
Switch the copyright holder to "The Android Open Source Project" to
match all the other source files in the tree, and move it to the top
of the file to match everything else we do.

Change-Id: Ie15d8e2bc004a626e45f715271deeaf3919dc44a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256235
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-20 23:11:17 +00:00
a1e24b1f00 tests: add git_require coverage
Change-Id: I0c8fb45f6d5808caf361240a3a0b68eef670eeaa
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256112
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-20 06:55:22 +00:00
e6e27b338b abandon: add support for --quiet
Also fix the normal output to write to stdout.

Change-Id: I6c117eea9cec08f5be9a44b90dbe9bf1f824ec95
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256114
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-20 06:14:00 +00:00
aa611a2ca2 sync: Fix flake8 E125 and E129 warnings
- E129 visually indented line with same indent as next logical line
- E125 continuation line with same indent as next logical line

Fixed automatically by:

 autopep8 --in-place --select E125,E129 subcmds/sync.py

Change-Id: Ia2f82f443e1e6a23ba22c6f9849c8485405aed0e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256092
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-20 02:17:08 +00:00
949bc34267 main/repo: add support for subcommand aliases
This supports [alias] sections with repo subcommands just like git.

Change-Id: Ie9235b5d4449414e6a745814f0110bd6af74ea93
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255833
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-20 00:53:46 +00:00
f841ca48c1 git_config: add support for repo-specific settings
This allows people to write ~/.repoconfig/config akin to ~/.gitconfig
and .repo/config akin to .git/config.  This allows us to add settings
specific to repo without mixing up git, and to persist in general.

Change-Id: I1c6fbe31e63fb8ce26aa85335349c6ae5b1712c6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255832
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-20 00:53:39 +00:00
c0d1866b35 project/sync: move DeleteProject helper to Project
Since deleting a source checkout involves a good bit of internal
knowledge of .repo/, move the DeleteProject helper out of the sync
code and into the Project class itself.  This allows us to add git
worktree support to it so we can unlock/unlink project checkouts.

Change-Id: If9af8bd4a9c7e29743827d8166bc3db81547ca50
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256072
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-20 00:51:42 +00:00
f81c72ed77 project: set core.repositoryFormatVersion=1 when using extensions
When using extensions, make sure we set the git repo format version
so git knows to check the extension compatibility.  We can add a
helper to the Project API to simplify this and make it foolproof.

Change-Id: I9ab6c32d92fe2b8e5df6e2b080ca71556332e909
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256035
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-02-19 23:44:10 +00:00
77b4397a73 git_config: add GetInt helper
Change-Id: Ic034ae2fd962299d1b352e597b391b6582ecf44b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256052
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-02-19 23:09:05 +00:00
0334b8c673 docs: improve project-objects & worktrees layout info
Make it clear that the paths have a .git suffix, and clarify the
reason for not using remote servers in the layout.

Change-Id: I62c6977ee6f4e1e9882d45727eb239cf5489d2b6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256033
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-19 21:58:43 +00:00
7ff80afdf6 upload: add a --hashtag-branch option akin to -t
This will automatically add the current local branch name as a hashtag.

Bug: https://crbug.com/gerrit/10477
Change-Id: I888f8be8419c801f2d98b7a2ad2486799e94f32c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255893
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-19 18:12:28 +00:00
19ec797f81 repo: reexec into Python 3 under Windows
Hopefully enough issues should be resolved now that we can start
forcing Windows users into Python 3 too.

Change-Id: Ic4aad6a0b35ffec7d1372e3da6fca11a2b6fde0b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255353
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-19 18:11:57 +00:00
979d5bdc3e add experimental git worktree support
This provides initial support for using git worktrees internally
instead of our own ad-hoc symlink tree.  It's been lightly tested
which is why it's not currently exposed via --help.

When people opt-in to worktrees in an existing repo client checkout,
no projects are migrated.  Instead, only new projects will use the
worktree method.  This allows for limited testing/opting in without
having to completely blow things away or get a second checkout.

Bug: https://crbug.com/gerrit/11486
Change-Id: Ic3ff891b30940a6ba497b406b2a387e0a8517ed8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254075
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-19 18:11:33 +00:00
56ce3468b4 assume environment always accepts strings
Different Python & OS versions have different environ behavior wrt
accepted types & encoding.  Since we're migrating to be Python 3 only,
lets change our code to assume strings always work as that's what the
newer Python 3 does.  This will fail under Python 2 for some env vars,
mostly on Windows, but the effort of maintaining shim layers that can
handle these edge cases isn't worth it when we're dropping that code.

We leave the logic in the `repo` launcher for now as it is simple, and
we want it to be able to switch versions a bit longer than the rest of
the tree.

Here's the support table:
          |    *NIX      |         Windows           |
 Python 2 | ASCII string | str or bytes, not unicode |
 Python 3 | str or bytes | str only                  |

Windows uses strings natively in its environment all the time.  But it
doesn't allow unicode strings under Python 2, so we have to encode.

Python 2 on *NIX is funky in that it always lowers to ASCII, so we had
to manually encode to avoid errors regardless of unicode or str.

Python 3 on Windows & *NIX will accept strings.  *NIX will also accept
bytes but Windows will not.

Bug: https://crbug.com/gerrit/12145
Change-Id: I3cf8f95a06902754ea1f08ad4b28503f7063531b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/248972
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-19 18:03:46 +00:00
02aa889ecd upload: add support for --yes
This adds a CLI option to the existing autoupload gitconfig knob that
allows people to automatically answer "yes" to the various prompts.

Bug: https://crbug.com/gerrit/12368
Change-Id: I819ebca01b9a40240b33866ae05907c7469703e3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255892
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-19 16:04:14 +00:00
819cc81c57 upload: add support for standard --dry-run
Change-Id: I69ea2f3170ba17bfb9e0e3771db4ecc66a736797
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255856
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-19 08:32:12 +00:00
84685ba187 upload: add support for setting hashtags
This allows users to specify custom hashtags when uploading, both via
the CLI and via the same gitconfig settings as other upload options.

Bug: https://crbug.com/gerrit/11174
Change-Id: Ia0959e25b463e5f29d704e4d06e0de793d4fc77c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255855
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-19 08:31:18 +00:00
72ebf19e52 command: add a repo help tip to --help output
For people used to running `repo xxx --help`, they might not realize
that there are detailed man pages behind `repo help xxx`.  Add a note
to all --help commands to improve discoverability.

Change-Id: I84af58aa0514cc7ead185f6c2534a8f88e09a236
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255853
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-19 08:23:04 +00:00
e50b6a7c4f project: handle verbose with initial clone bundle
If we're not in --verbose mode with repo sync, then omit the
per-project clone bundle progress bar.

Bug: https://crbug.com/gerrit/11293
Change-Id: Ibdf3be86d35fcbccbf6788c192189f38c577e6e9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255854
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-19 08:01:40 +00:00
8a98efee5c main: fix pager logic after negation cleanup
The pager setting is tristate (where None means "auto"),
so make sure we still handle that setting.

Change-Id: I89fe352572dd15922c61e3bb65ac33f847d01ee0
Test: `repo help upload` triggers the pager
Test: `repo -p help upload` triggers the pager
Test: `repo --no-pager help upload` doesn't trigger the pager
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255852
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-19 08:00:11 +00:00
7a753b8b18 upload: improve no pending CL error handling
Show clearer messages and exit non-zero if there's nothing to upload.

Change-Id: Icd9c13b9b1126610a409fc13d1d11bfc66f5e802
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255834
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-19 05:46:43 +00:00
0258584c72 docs: add per-project review/remote/branch settings
Change-Id: Iae7dc438b4a145140b4e105a61024a11e30b2c2b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255792
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-19 00:26:34 +00:00
c58ec4dba1 avoid negative variables
Trying to use booleans with names like "no_xxx" are hard to follow due
to the double negatives.  Invert all of them so we only have positive
meanings to follow.

Change-Id: Ifd37d0368f97034d94aa2cf38db52c723ac0c6ed
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255493
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-19 00:24:43 +00:00
e1191b3adb Open temporary cookie file as writable in sync.py
Named Temporary file defaults to mode 'w+b' which causes repo sync to
fail. By opening the tmpcookiefile in PersistentTransport.request as
writable, we are able to run sync successfully.

Bug: https://crbug.com/gerrit/12370
Test: Ran smartsync successfully
Change-Id: I01ddf915fc30eb3ff0e4d440a6f1aa261c63e88d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255692
Tested-by: Jonathan Nieder <jrn@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
2020-02-18 19:20:53 +00:00
8f9bf484d8 platform_utils: have Windows select stream return "" at EOF
This matches *NIX behavior where the last read is '', not None.

Bug: https://crbug.com/gerrit/12329
Change-Id: I48b026b4d1b8d7c6abbce198757b970931869e1a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255352
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-18 19:06:02 +00:00
37f28f1b4e main: add python version checking
If an older launcher script is used with newer repo source tree, we
might be issuing python version warnings.  Plus, we want to be able
to roll Python version requirements independently of the launcher.
Add some version checking here too.

Change-Id: Ia35fc821f93c429296bdf5fd578276fef796b649
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255592
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-18 14:38:33 +00:00
af1e5dea35 resort a few module imports to follow PEP8
All the stdlib imports are supposed to come before any local imports.

Change-Id: I10c0335ba2ff715fd34c9eb91bfe6560e904df08
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255593
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-18 06:28:12 +00:00
3cceda535d project: Fix E125 continuation line with same indent as next logical line
Change-Id: I71d2b105baacf6968a29391e9e2a74bba1b4fd0b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255555
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-18 05:53:51 +00:00
31990f0097 project: move successful fetch output behind verbose
Syncing projects works fine the majority of the time.  So rather than
dump all of that noisy output to stdout, lets capture it and only show
when things fail or in verbose mode.  This tidies up the default `repo
sync` output.

Bug: https://crbug.com/gerrit/11293
Change-Id: I8314dd92e1e6aadeb26e36a8c92610da419684e6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255413
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-18 03:31:33 +00:00
16f2fae16f diff: delete unused nested func
Change-Id: I43ab4bc944269e43a6cd7b2ac350c09b7c700a6c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255492
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-17 23:49:47 +00:00
521d01b2e0 sync: introduce --verbose option
This allows us to control sync output better by having three levels
of output: quiet (only errors), default (progress bars), verbose (all
the things).  For now, we just put the chatty "already have persistent
ref" message behind the verbose level.

Bug: https://crbug.com/gerrit/11293
Change-Id: Ia61333fd8085719f3e99edb7b466cdb04031b67f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255414
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-17 17:02:27 +00:00
2b1345b8c5 project: disable stat output when fast forwarding merges
Our sync output is pretty chatty, and the stat output on fast forward
merges doesn't really help.  Suppress it to tighten up the output.

Change-Id: I91e50639b3cd8db9df3d13a7da6d1aaa70d7932f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255412
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-17 06:55:08 +00:00
3995ebd8c1 Update commit-msg hook to version from Gerrit 3.1.3
Includes the following commits (redacted to those that are relevant):

da300bd9bd8 - Do not create a change id if gerrit.createChangeId == false
731eb42b8ae - Do not strip out "-- >8 --" comment in commit-msg hook
627d07c2bfc - Handle messages with only comments in the commit-msg hook
68296f71804 - Simplify the hook script using git-interpret-trailers

Change-Id: I7a82836495427df3c5437ba88a9576b47629065f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255393
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-17 03:57:19 +00:00
b57e633433 github: enable github actions for postsubmit testing
This gives us a bit of feedback by running our testsuite on Linux,
macOS, and Windows platforms.   While Linux & macOS are passing,
Windows fails some of them.  We can figure that out later.  This
is better than what we have now which is manual one-offs.

Change-Id: I9d2d644be97ec76645db0bc15739e7679310a647
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255314
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-16 05:12:33 +00:00
d21638424c tox: get tests passing a bit on Windows
We need to use the path separators provided by the python library,
and we need to set the git env vars so the name is always known.
Not all tests pass, but at least the basic frameworks work now.

Change-Id: Icea67098a8d7d58bbf918c78325681cf12a2e5f2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255313
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-15 23:25:25 +00:00
c102fd5c0d README: add <> around links
Some markdown renderers want <> around links to linkify them.
Other renderers strip them out as redundant.

Change-Id: Ib7f9962ce1dd47b4494a824c69358c75d98eb838
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255312
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-15 23:24:29 +00:00
d6b8bd464c Reword the documentation regarding coding style
- flake8 is a wrapper around pyflakes, so it's redundant to mention
  both of them. Roll the explicit sections about coding errors and
  coding style violations into a single section.

- After recent cleanups the project now has zero warnings or errors
  from flake8. Reword the requirements so that it is now mandatory
  to not introduce new warnings.

- Expand the section on suppression of warnings to differentiate
  between suppressing inline individually and globally suppressing
  for the whole project.

- Properly capitalize "Python Style Guide".

Change-Id: I4b333d013e985db252873441b16cb719ed5be5b5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255040
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-15 23:18:50 +00:00
6a784ff9a6 repo: handle bad programs a bit better
If programs emit non-UTF-8 output, we currently throw a fatal error.
We largely only care about the exit status of programs, and even the
output we do parse is a bit minimal.  Lets make it into a warning and
mangle the invalid bytes into U+FFFD.  This should complain enough to
annoy but not to break when it's not necessary.

Bug: https://crbug.com/gerrit/12337#c2
Change-Id: Idbc94f19ff4d84d2e47e01960dd17d5b492d4a8a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255272
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-15 19:32:28 +00:00
a46bf7dc2a flake8: Suppress "F821 undefined name" inline for Python 2 names
All of the instances of this are related to Python 2 names that
don't exist in Python 3, and the warnings are raised when running
flake8 on Python 3.

All of these will go away once we completely remove support for
Python 2, so just suppress them inline. We don't globally suppress
the check so that we will still see legitimate errors if/when they
occur in new code.

Change-Id: Iccf955f50abfc9f83b371fc0af6cceb51037456f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255039
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-15 04:45:16 +00:00
19a1f22cd0 repo: rework gpg import for Windows
Some versions of gpg on Windows mishandle native paths with homedir.
It manifests itself like:

gpg: keybox 'C:\Users\.../.repoconfig\gnupg/pubring.kbx' created
gpg: C:\Users\.../.repoconfig\gnupg/trustdb.gpg: trustdb created
gpg: key 16530D5E920F5C65: public key "Repo Maintainer <repo@android.kernel.org>" imported
gpg: can't connect to the agent: Invalid value passed to IPC
gpg: Total number processed: 1
gpg:               imported: 1
fatal: registering repo maintainer keys failed

It seems gpg (at least version 2.2.17) needs paths to be specified
in cygwin form (e.g. "/c/Users/.../.repoconfig/gnupg") otherwise
it fails to talk to its own processes.  We can work around this
with a minor trick: we cd to the right path and then invoke gpg
with --homedir . and let gpg itself resolve . to whatever form it
really wants.

This is a bit hacky, but we don't control gpg, and this allows us
to avoid having to muck with the environment.  Since --homedir has
been around since at least gpg-1.4.x from 2004, backwards compat
shouldn't be an issue.

While we're here, touch up the output a bit: there's no need to
dump all the chatty gpg output if things don't fail, so always
swallow the output.  If things do fail, our exception handler
takes care of dumping the full stdout & stderr.

Change-Id: I74ab98e1e61e95318fda6faf57c6a8699f775935
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255120
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-15 03:55:45 +00:00
076512aafa flake8: Suppress "E731 do not assign a lambda expression, use a def"
The Google Python Style Guide [1] says that lambdas are OK for
one-liners. All the current usages are one-liners, so let's just
suppress it.

[1] http://google.github.io/styleguide/pyguide.html#210-lambda-functions

Change-Id: I404c7a8e5e71870caf0f4604862cbf01db495863
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255038
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-15 03:41:17 +00:00
d8fda90eed repo: rework parser setup to handle init -c
We added support for `repo init -c` to main.py, but not to the
launcher, so the -c option only works after the first init has
run which kind of defeats its purpose.  Rework the parser setup
so that we can tell it whether it's for "init" or "gitc-init"
and then add the -c option in the same way we do in main.py.

This has the benefit of getting the parser entirely out of the
module scope which makes it a lot easier to reason about, and
it means we can write some unittests.

Change-Id: Icbc2ec3aceb938d5a8f941d5fbce1548553dc5f7
Test: repo help init
Test: repo help gitc-init
Test: repo init -u https://android.googlesource.com/platform/manifest -c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255113
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-14 05:52:17 +00:00
9cc1d70476 repo: add some newer RSA/ECC signing keys
We've been using a DSA/1024 key to sign our tags.  Time to update to
something a bit newer.  We'll include RSA & ECC keys, but only use
RSA keys initially for backwards compatibility and see how it goes
with our user base.

Change-Id: I683c97b6fbd860f220ed4ddc7b21f07db279a916
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255112
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-14 04:59:47 +00:00
c19cc5c508 repo: Fix warnings reported by flake8
repo:342:5: E306 expected 1 blank line before a nested definition, found 0
  repo:617:5: F841 local variable 'ret' is assigned to but never used

Change-Id: I364fdb5dac8ebaff398b848935fe8356cb9ed2d3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/255035
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-14 00:42:23 +00:00
6fb0cb5c80 repo: add trace support to the launcher
Now that we have a central run_command point, we can easily add
tracing support to the launcher script.

Change-Id: I9e0335c196cafd6263ff501925abfe835f036c5e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254755
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-13 07:02:07 +00:00
62285d22c1 repo: add some helpers akin to subprocess.run
We can't rely on subprocess.run yet as that requires Python 3.6,
but we can clean up the code we have with some ad-hoc replacement.
This unifies all the inconsistent subprocess.Popen usage we have.

Change-Id: I56af40a3df988ee47b299105d692ff419d07ad6b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254754
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-13 06:57:35 +00:00
3cda50a41b pyflakes: Fix remaining "E501 line too long" warnings
We increased the max line length to 100 columns which got rid of
the majority of these warnings, but there were still a few lines
that exceeded 100 columns.

Change-Id: Ib3372868ca2297f83073a14f91c8ae3df9d0d0e6
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254699
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-13 04:54:10 +00:00
afbccdb11e Update .mailmap
Change-Id: I502a07e7702b73db9f0933cbfd4007c119e3463a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254700
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-13 04:49:55 +00:00
e8ace26117 project: Don't emit locally modified hook warning in quiet mode
Change-Id: I0f6db037b85f2a015fc7b7fd37472df848a58266
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254698
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-13 04:12:38 +00:00
daa2cecdc5 Mention exceptions to Google Style Guide in SUBMITTING_PATCHES.md
Change-Id: I05d313c66f312942405a884a54118cb1d7af1bac
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254671
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-13 04:12:22 +00:00
3c5114cd78 Don't format version to 5 characters in new version message
Change-Id: I6c734170173f77a6fef0678f189e198bdaeec425
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254668
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-13 01:27:25 +00:00
7838e388ac Replace 'A new repo command' with 'A new version of repo'
Change-Id: I3288f5c963b69d05d113fc039e4b4f22721f1de9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254667
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-13 01:27:25 +00:00
aa47181e36 repo: Remove duplicate import of 'os'
Change-Id: I9874a5deacdb6a8ce98a8a383326a5b41b1518df
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254697
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-13 00:31:24 +00:00
58a8b5c5d9 repo: Remove another usage of bare 'except'
Change-Id: I9195b40f5af7cbf74b47376a4708de82495f8fba
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254696
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-13 00:31:24 +00:00
22dbfb99e5 repo: Remove unused variable in 'except'
Change-Id: I90f89ed6638a3d2a9e665ebbedef5dd7902f5429
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254695
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-13 00:31:24 +00:00
31b9b4b06c repo: Fix blank line issues reported by flake8
Change-Id: I62633e71a36b2acbd09e205447a02159dd334896
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254694
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-13 00:31:24 +00:00
0b57eed8f0 repo: bump launcher version for accumulated fixes
Change-Id: I5d9b866cc53d3824a01f5f0af127cf0c3ff97366
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254757
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 23:27:11 +00:00
72b6dc8891 repo: avoid bare excepts to allow SystemExit to bubble
Bug: https://crbug.com/gerrit/12327
Change-Id: I4ce1142379b111f9ba3a2e5a437026e5c0378a9e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254756
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 22:21:52 +00:00
e19d9e1a65 sync: add a "finished" message
Some people find the existing output to be a bit confusing.  It spews
a lot of git output before exiting, but it's not exactly clear what
the final state is when things pass.  Add an explicit message.

Bug: https://crbug.com/gerrit/10501
Change-Id: I9de83b595d3185feb820005b8fc81c6adc55b357
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254732
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 20:54:57 +00:00
8ddff5c74f repo: add --version support to the launcher
We can get version info when in a checkout, but it'd be helpful
to show that info at all times.

Change-Id: Ieeb44a503c9d7d8c487db4810bdcf3d5f6656c82
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254712
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 20:54:50 +00:00
8409410aa2 repo: export GIT_TRACE2_PARENT_SID
This helps with people tracing repo/git execution.  We use a similar
format to git, but a little simpler since we always initialize the
env var setting, and we want to avoid too much overhead.

Bug: https://crbug.com/gerrit/12314
Change-Id: I75675b6cc4c6f7c4f5e09f54128eba9456364d04
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254331
Reviewed-by: Josh Steadmon <steadmon@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 19:57:42 +00:00
dc63181fcd flake8: Add comments in config to explain suppressed checks
Change-Id: Ib5c09b36d40a96ba9167b42b3bd2f1ed072660b7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254611
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 12:33:02 +00:00
f700ac79c3 repo: move parser init out of module scope
We import the wrapper on the fly, so minimize how much code we run
in module scope.  It's pointless/wasted when importing.

Change-Id: I4a71c2030325d0a639585671cd7ebe8f22687ecd
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254072
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 11:46:30 +00:00
6f1c626a9b drop old git_require checks
We've been requiring git-1.7.2 since Oct 2012, so we can safely drop
the individual checks sprinkled throughout the code base for older.

Change-Id: I1737fff7b3f27f475960b0bff9cb300aefd5d108
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253135
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 11:44:59 +00:00
77479863da flake8: Ignore 'line break before/after binary operator'
- W503 line break before binary operator
- W504 line break after binary operator

There doesn't seem to be a nice way of fixing all of these without
replacing W503 with W504 or vice-versa, or unwrapping them resulting
in excessively long lines. Let's just suppress them.

Change-Id: I7846d0124054f58e1cb480d4459cd9c86b737a50
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254608
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 07:29:25 +00:00
16a5c3ac51 git_config: Stop using backslash to wrap lines
Unwrap one unnecessarily wrapped line, and use parentheses on
a wrapped condition instead of wrapping with backslashes.

Change-Id: I12679a0547dd822b15a6551e0f6c308239ff7b2d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254607
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 07:29:25 +00:00
145e35b805 Fix usage of bare 'except'
flake8 reports:

  E722 do not use bare 'except'

Replace them with 'except Exception' per [1] which says:

  Bare except will catch exceptions you almost certainly don't want
  to catch, including KeyboardInterrupt (the user hitting Ctrl+C) and
  Python-raised errors like SystemExit

  If you don't have a specific exception you're expecting, at least
  except Exception, which is the base type for all "Regular" exceptions.

[1] https://stackoverflow.com/a/54948581

Change-Id: Ic555ea9482645899f5b04040ddb6b24eadbf9062
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254606
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 06:49:25 +00:00
819827a42d Fix blank line issues reported by flake8
- E301 expected 1 blank line
- E302 expected 2 blank lines
- E303 too many blank lines
- E305 expected 2 blank lines after class or function definition
- E306 expected 1 blank line before a nested definition

Fixed automatically with autopep8:

  git ls-files | grep py$ | xargs autopep8 --in-place \
    --select E301,E302,E303,E305,E306

Manually fix issues in project.py caused by misuse of block comments.

Change-Id: Iee840fcaff48aae504ddac9c3e76d2acd484f6a9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254599
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 06:36:40 +00:00
abdf750061 Fix indentation issues reported by flake8
- E121 continuation line under-indented for hanging indent
- E122 continuation line missing indentation or outdented
- E125 continuation line with same indent as next logical line
- E126 continuation line over-indented for hanging indent
- E127 continuation line over-indented for visual indent
- E128 continuation line under-indented for visual indent
- E129 visually indented line with same indent as next logical line
- E131 continuation line unaligned for hanging indent

Fixed automatically with autopep8:

  git ls-files | grep py$ | xargs autopep8 --in-place \
    --select E121,E122,E125,E126,E127,E128,E129,E131

Change-Id: Ifd95fb8e6a1a4d6e9de187b5787d64a6326dd249
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254605
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 06:36:22 +00:00
0ab95ba6d0 git_config: Unwrap unnecessarily wrapped line
Change-Id: I56806e8b9b09cd0f7fb834d7edc412682f2af1db
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254604
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 06:32:47 +00:00
5a2517f411 command: Add parentheses on wrapped condition
Surround the condition with parentheses rather than using
backslashes. This prevents confusion about indentation when
running flake8/autoflake8.

Change-Id: I01775b96f817ee616f545b55369a4864fa1d6712
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254603
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 06:32:47 +00:00
54a4e6007a Fix various whitespace issues reported by pyflakes
- E201 whitespace after '['
- E202 whitespace before '}'
- E221 multiple spaces before operator
- E222 multiple spaces after operator
- E225 missing whitespace around operator
- E226 missing whitespace around arithmetic operator
- E231 missing whitespace after ','
- E261 at least two spaces before inline comment
- E271 multiple spaces after keyword

Fixed automatically with autopep8:

  git ls-files | grep py$ | xargs autopep8 --in-place \
    --select E201,E202,E221,E222,E225,E226,E231,E261,E271

Change-Id: I367113eb8c847eb460532c7c2f8643f33040308c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254601
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 06:00:16 +00:00
42339d7e52 Remove redundant backslashes
fleka8 reports:

  E502 the backslash is redundant between brackets

Fixed automatically with autopep8:

  git-repo $ git ls-files | grep py$ | xargs autopep8 --in-place --select E502

Change-Id: I1486ae1d17206918474363daf518274c5be8daed
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254602
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 05:45:44 +00:00
03ae99290a pager: Remove unnecessary semicolons
flake8 reports:

  E703 statement ends with a semicolon

Change-Id: Ia63fc9efb04425e425c0f289272db76ff1ceeb34
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254600
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 05:40:33 +00:00
9090e804ab Remove unused imports
flake8 reports:

  F401 'name' imported but unused

Change-Id: Id45d6efa87ddf53f2c4a0f0c4136ea361ab1b746
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254592
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 05:40:10 +00:00
eeff3537de Fix tests for membership to use 'not in'
flake8 reports:

  E713 test for membership should be 'not in'

Change-Id: I4446be67c431b7267105b53478d2ceba2af758d7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254451
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 05:18:17 +00:00
8f78a83083 upload: Fix tests for object identity to use 'is not'
flake8 reports:

  E714 test for object identity should be 'is not'

Change-Id: Ib8c4100babaf952bbfe65fd56555ece8a958e4b0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254450
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 05:17:49 +00:00
e5913ae410 Fix flake8 E251 unexpected spaces around keyword / parameter equals
Fixed automatically with autopep8:

  git ls-files | grep py$ | xargs autopep8 --in-place --select E251

Change-Id: I58009e1c8c91c39745d559ac919be331d4cd9e77
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254598
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 05:17:08 +00:00
119085e6b1 flake8: Increase max line length from 80 to 100
The Google style guide for python [1] says the maximum line length
should be 80, but there are several lines in the code base that
exceed it:

  git ls-files | grep py$ | xargs flake8 | grep E501 | wc -l
  64

I don't think it's worth going through and re-wrapping all those,
so just increase the limit to 100 which seems to be a reasonable
compromise:

  git ls-files | grep py$ | xargs flake8 | grep E501 | wc -l
  6

Leave the re-rewrapping of those lines for a follow-up commit,
though.

[1] http://google.github.io/styleguide/pyguide.html#32-line-length

Change-Id: Ia37c34301163431fd1fb4fb6697a4a482d6be077
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254595
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 05:16:11 +00:00
086710465e upload: Fix flake8 E241 multiple spaces after ','
Change-Id: I3a65869f9d006027270a7826d7982950c0e6759a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254597
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 05:00:36 +00:00
ed4f2113d2 project: make syncing a little more self-healing
We have a few files that we optionally symlink from the work tree
.git/ to the .repo/projects/ path.  If they don't exist when we
first initialize, then we skip creating symlinks.  If the files
are created later on under the work tree .git/, repo gets upset.

This can happen with the packed-refs file: if we don't have any
packed refs initially, we don't symlink it.  But if git tries to
pack refs later on and creates the file, the project gets wedged.

We could create an empty file initially and then symlink it, but
for some files, it's not clear we want to always do that (e.g.
the .git/shallow setting).  Instead, lets make handling of these
paths more dynamic.  If they show up later on in the work tree
.git/ only, we'll take care of relocating & symlinking.  This
also makes repo a little more robust and autorecovers incase a
path goes missing in one of the dirs.

Ideally we wouldn't monkey around at all here, but considering
the only option we give to users currently is to blow things
away with --force-sync, this seems a bit better.

Bug: https://crbug.com/gerrit/12324
Change-Id: Ia6960f1896ac6d890c762d7d053684a1c6ab2c87
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254632
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 04:48:36 +00:00
719675bcec info: Fix formatting of block comment
flake8 reports:

  E265 block comment should start with '# '

While we're at it, add a period at the end of the comment sentence.

Change-Id: Icb7119079a1d64e6defafc3f6d24e99dbf16139d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254596
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 04:31:40 +00:00
21c1575ee4 upload: add a --ignore-hooks option
When upload hooks fail, people are forced to use --no-verify to upload
CLs anyways.  When projects have flaky hooks, this trains people to
always use that option.  This is obviously bad: hooks might get fixed,
or some of the hooks are always good & people should review.

Lets add an --ignore-hooks option.  This still runs the hooks, but any
failures will be ignored and allow the user to upload anyways.

Bug: https://crbug.com/gerrit/12230
Change-Id: Ide2ac8a40a656bfcd6aae20c3ce8118e06bf909b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254452
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 04:18:49 +00:00
8f9e02231a Remove trailing blank lines
flake8 reports:

  W391 blank line at end of file

Change-Id: I5498b2de2d1268d4f1f4b9e1760f9fa93a6da4cd
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254594
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 02:58:17 +00:00
348e218d5b test_project.py: Remove unused variable in 'with' statement
flake8 reports:

 F841 local variable 'f' is assigned to but never used

Change-Id: If808eb381ee44c7da71e6281615a06a6723cf945
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254593
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-12 02:56:32 +00:00
4bbba7d627 Fix duplicate method name in test_project.py
flake8 reports:

  F811 redefinition of unused 'test_src_block_dir' from line 259

which is caused by having two methods with the same name. Rename
them both to better desribe their purpose.

Change-Id: If7612a42001776d71bb1a6a80fc631d3d262e6ce
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254449
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 02:55:51 +00:00
d92076d930 Revert "Save cookies back to jar when fetching clone.bundle"
This reverts commit 4abf8e6ef8.

The curl process for updating the cookie file is not atomic.  When
fetching many bundles in parallel, we can sometimes corrupt the file
causing it to be cleared.  Since users should manage gitcookies on
their own, leave it read-only.

Bug: https://crbug.com/gerrit/12300
Change-Id: Id472c99b197bc4cf8533c649f8881509f38643c1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254092
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
(cherry picked from commit dc1d0e0c7f)
2020-02-11 21:03:35 -05:00
dc1d0e0c7f Revert "Save cookies back to jar when fetching clone.bundle"
This reverts commit 4abf8e6ef8.

The curl process for updating the cookie file is not atomic.  When
fetching many bundles in parallel, we can sometimes corrupt the file
causing it to be cleared.  Since users should manage gitcookies on
their own, leave it read-only.

Bug: https://crbug.com/gerrit/12300
Change-Id: Id472c99b197bc4cf8533c649f8881509f38643c1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254092
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 02:00:16 +00:00
82caef67a1 repo: lower min version of git a bit
We were perhaps a bit too hasty to jump to git-2.10.  Existing LTS
releases of Ubuntu are quite old still: Trusty has 1.9 while Xenial
has 2.5.  While we plan on dropping support for those eventually as
we migrate to Python 3.6, we don't need to be so strict just yet on
the git versions.

We also want to disconnect the version the repo launcher requires
from the version the rest of the source tree requires.  The repo
launcher doesn't need as many features, and being flexible there
allows us more freedom to upgrade & rollback as needed.

So we'll allow git-1.7 again, but start warning on any users older
than git-1.9.  This aligns better with existing LTS releases, and
gives users a chance to start upgrading before we cut them off.

Change-Id: I140305dd8e42c9719c84e2aee0dc6a5c5b18da25
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254573
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 00:28:03 +00:00
3645bd2420 docs: document git/python/Ubuntu/Debian release schedules
Going purely on upstream package release cycles doesn't tell the whole
story: a lot of people run LTS distros which will have older versions
of software we want to support.

Build out a table for us to quickly reference when making decisions as
to what versions of git/python we want to support, and when we can drop
them.  This will also help to refer users to as why we made a specific
decision that might be affecting them.

Change-Id: I7aea24bbefd50e358aeacf11e8c15a346c8fb8a9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254572
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-12 00:27:59 +00:00
aeb2eee9d3 repo: bump launcher version
This way we can push out the updated stable branch change.

Change-Id: I72d5dab4523a10dfeb6529796892096aa80eba3c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254492
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-12 00:12:14 +00:00
5f2b045195 sync: change how we preserve objects in shared repos
Some automatic git operations will prune objects on us, and not just
the gc step.  Normally we don't care, but with shared projects, we
will have multiple git checkouts with refs that the others cannot
see, but with a shared object dir.  Any pruning of objects based on
refs in just one repo can easily break the others.

git-2.7.0 introduced a preciousObjects setting which tells git to
never prune objects for this exact scenario: there might be refs in
some location that git is unable to see.

Change-Id: I781de27c5bbe1d4c70f0187566141c9cce088bd8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254392
Reviewed-by: Nasser Grainawi <nasser@codeaurora.org>
Reviewed-by: David Riley <davidriley@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-11 23:58:43 +00:00
45d1c372a7 project: fix bytes/str encoding when updating git submodules
Since tempfile.mkstemp() returns a file handle in binary mode,
make sure we turn our strings into bytes before writing.

Bug: https://crbug.com/gerrit/12043
Change-Id: I3e84d595e84b8bc12a1fbc7fd0bb3ea0ba2832b0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254393
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
(cherry picked from commit 163d42eb43)
2020-02-11 14:46:13 -05:00
19607b2817 repo: allow REPO_REV to be an env var
We do this for REPO_URL already.

Bug: https://crbug.com/gerrit/10233
Change-Id: I53410645474b00d900467c96fa5d8446f3a607d3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253552
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
(cherry picked from commit 563f1a6512)
2020-02-11 14:44:29 -05:00
163d42eb43 project: fix bytes/str encoding when updating git submodules
Since tempfile.mkstemp() returns a file handle in binary mode,
make sure we turn our strings into bytes before writing.

Bug: https://crbug.com/gerrit/12043
Change-Id: I3e84d595e84b8bc12a1fbc7fd0bb3ea0ba2832b0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254393
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-11 18:49:47 +00:00
68744dbc01 Fixing forall subcommand for Py3
Execution of 'repo forall -p -c' doesn't work with Py3 and ends up
with an error:

Got an error, terminating the pool: TypeError: can only concatenate
str (not "bytes") to str

That's fixed by using the decode() method.

Change-Id: Ice01aaa1822dde8d957b5bf096021dd5a2b7dd51
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253659
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Jiri Tyr <jiri.tyr@gmail.com>
(cherry picked from commit 83a3227b62)
2020-02-10 23:31:45 -05:00
ef412624e9 remove spurious +x bits
These files are not directly executable, so drop the +x bits.

Change-Id: Iaf19a03a497686cc21103e7ddf08073173440dd1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254076
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
(cherry picked from commit e7c91889a6)
2020-02-10 23:31:03 -05:00
a06ab7d28b find python via env
This allows these scripts to run through the active version of the
virtualenv python when invoked via tox.

Change-Id: Ib52f475b7b20c34d62cfd179a1341da1a08a8b5c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253974
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
(cherry picked from commit 1b117db767)
2020-02-10 23:30:58 -05:00
471a7ed5f7 git_config: fix encoding handling in GetUrlCookieFile
Make sure we decode the bytes coming from the subprocess.Popen as
we're treating them as strings.

Change-Id: I44100ca5cd94f68a35d489936292eb641006edbe
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253973
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
(cherry picked from commit ded477dbb9)
2020-02-10 23:28:40 -05:00
07392ed326 project: allow src=. with symlinks
Some Android/Nest manifests are using <linkfile> with src="." to
create stable paths to specific projects.  Allow that specific
use case as it seems reasonable to support.

Bug: https://crbug.com/gerrit/11218
Change-Id: I16dbe8d9fe42ea45440afcb61404c753bff1930d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254330
Reviewed-by: Chanho Park <parkch98@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-11 04:23:26 +00:00
3285e4b436 main: rework launcher version checking
The code has an ad-hoc check in that it requires the launcher major
version to not be less than the source code version.  We don't really
care about that requirement, and it doesn't fit with our other version
checks.  Rework it so we explicitly declare the min launcher version
that is supported.

We'll start with requiring repo launcher 1.15 which was released back
in 2012.  Hopefully no one has anything older than that, although it's
not clear we work with even newer versions than that :).  But let's be
a little conservative with the first update to this logic.

Bug: https://crbug.com/gerrit/10418
Change-Id: I611d70c60324d313c76874e978b8499a491a5d00
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254278
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-10 23:20:55 +00:00
ae62541005 manifest_xml: allow src=. with symlinks
Some Android/Nest manifests are using <linkfile> with src="." to
create stable paths to specific projects.  Allow that specific
use case as it seems reasonable to support.

Bug: https://crbug.com/gerrit/11218
Change-Id: I5eadec257cd58ba0f8687c590ddc250a7a414a85
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254276
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-10 23:19:31 +00:00
83a3227b62 Fixing forall subcommand for Py3
Execution of 'repo forall -p -c' doesn't work with Py3 and ends up
with an error:

Got an error, terminating the pool: TypeError: can only concatenate
str (not "bytes") to str

That's fixed by using the decode() method.

Change-Id: Ice01aaa1822dde8d957b5bf096021dd5a2b7dd51
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253659
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Jiri Tyr <jiri.tyr@gmail.com>
2020-02-10 10:52:27 +00:00
09dd9bda38 docs: document internal manifests.git/config settings
Change-Id: I6b32d925756375a9335522ff33376cb5f7ed1157
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254073
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-10 00:12:17 +00:00
f914edca53 project: unify HEAD path management
Add a helper function to unify the duplication of finding the full
path to the symbolic HEAD ref.  This makes it easy to handle git
worktrees where .git is a file rather than a dir/symlink.

Bug: https://crbug.com/gerrit/11486
Change-Id: I9f794f1295ad0d98c7c13622f01ded51e4ba7846
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254074
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-09 23:26:05 +00:00
e7c91889a6 remove spurious +x bits
These files are not directly executable, so drop the +x bits.

Change-Id: Iaf19a03a497686cc21103e7ddf08073173440dd1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254076
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-09 23:24:03 +00:00
1b117db767 find python via env
This allows these scripts to run through the active version of the
virtualenv python when invoked via tox.

Change-Id: Ib52f475b7b20c34d62cfd179a1341da1a08a8b5c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253974
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-09 04:02:45 +00:00
563f1a6512 repo: allow REPO_REV to be an env var
We do this for REPO_URL already.

Bug: https://crbug.com/gerrit/10233
Change-Id: I53410645474b00d900467c96fa5d8446f3a607d3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253552
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-09 04:02:26 +00:00
b4687ad862 docs: add a developer reference for .repo/ paths
Currently the only reference for these is the source which can be a
pita when needing to refer to something quickly.

Change-Id: I52baeb9a4935814cf99fa9a9b3102e8e46cddb0d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253972
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-09 03:55:47 +00:00
ded477dbb9 git_config: fix encoding handling in GetUrlCookieFile
Make sure we decode the bytes coming from the subprocess.Popen as
we're treating them as strings.

Change-Id: I44100ca5cd94f68a35d489936292eb641006edbe
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253973
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-08 06:26:36 +00:00
619a2b5887 Fix inverted logic around [gitc-]init and -c
Instead of not using '-c' for '--current-branch' when using gitc, we
were only using '-c' when using gitc, so we still had the conflict with
the gitc option, and other users still couldn't use '-c'.

Test: repo init -u https://android.googlesource.com/platform/manifest; repo init -c
Test: repo gitc-init -u ... -b ... -c testing
Change-Id: I71e4950a49c281418249f0783c6a2ea34f0d3e2b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253795
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Dan Willemsen <dwillemsen@google.com>
(cherry picked from commit 93293ca47f)
2020-02-07 15:54:52 -05:00
93293ca47f Fix inverted logic around [gitc-]init and -c
Instead of not using '-c' for '--current-branch' when using gitc, we
were only using '-c' when using gitc, so we still had the conflict with
the gitc option, and other users still couldn't use '-c'.

Test: repo init -u https://android.googlesource.com/platform/manifest; repo init -c
Test: repo gitc-init -u ... -b ... -c testing
Change-Id: I71e4950a49c281418249f0783c6a2ea34f0d3e2b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253795
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Dan Willemsen <dwillemsen@google.com>
2020-02-07 20:54:34 +00:00
dbd277ce50 [Win32] Make platform_utils compatible for Python3
On Python 3 several imports are to be imported from
different locations.

Signed-off-by: Remy Böhmer <linux@bohmer.net>
Change-Id: I4f243d145f65e38f74743a742583cfc5c5d76deb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/249610
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-06 14:29:15 +00:00
ab15e42fa4 Do not try to fetch default revision for mirrors always
* Mirrors may contain multiple projects, some of which may not
  always contain the default revision.
* Only fetch the default revision explicitly if
  '--current-branch' is set.
* Fixes breakage casued by
  commit 6856f98467
  "Fix repo mirror with --current-branch"

Bug: https://crbug.com/gerrit/12274
Change-Id: Iaafabe2992f76f3644b841f24245d3e19c9515a9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253093
Reviewed-by: Kuang-che Wu <kcwu@chromium.org>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Chirayu Desai <chirayudesai1@gmail.com>
(cherry picked from commit f7b64e3350)
2020-02-06 09:19:35 -05:00
5a03308c5c sync: try to checkout repos across sync failures
Currently our default behavior is:
* Try to sync all repos
  * If any errors seen, exit
* Try to garbage collect all repos
  * If any errors seen, exit
* Try to update local project list
  * If any errors seen, exit
* Try to checkout out all local repos
  * If any errors seen, exit

Users find these incomplete syncs confusing, so lets try to complete
as much as possible by default and printing out summaries at the end.

Bug: https://crbug.com/gerrit/11293
Change-Id: Idd17cc9c3bbc574d8a0f08a30225dec7bfe414cb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/238554
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-05 21:37:20 +00:00
3ba716f382 repo: try to reexec self with Python 3 as needed
We want to start warning about Python 2 usage, but we can't do it
simply because the shebang is /usr/bin/python which might be an old
version like python2.7.

We can't change the shebang because program name usage is spotty at
best: on some platforms (like macOS), it's not uncommon to not have
a `python3` wrapper, only a major.minor one like `python3.6`.  Using
python3 wouldn't guarantee a new enough version of Python 3 anyways,
and we don't want to require Python 3.6 exactly, just that minimum.

So we check the current Python version.  If it's older than the ver
of Python 3 we want, we search for a `python3.X` version to run.  If
those don't work, we see if `python3` exists and is a new enough ver.
If it's not, we die if the current Python 3 is too old, and we start
issuing warnings if the current Python version is 2.7.  This should
allow the user to take a bit more action by installing Python 3 on
their system without having to worry about changing /usr/bin/python.

Once we require Python 3 completely, we can simplify this logic a bit
by always bootstrapping up to Python 3 and failing with Python 2.

We have a few KI with Windows atm though, so keep it disabled there
until the fixes are merged.

Bug: https://crbug.com/gerrit/10418
Change-Id: I5e157defc788e31efb3e21e93f53fabdc7d75a3c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253136
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-05 21:32:02 +00:00
655aedd7f3 repo: raise min version of git
The git-2.10 series was released in 2016.  Since we're moving to
require Python 3.6 which was also released in 2016, bumping up the
git version seems reasonable.  Also we don't really test any git
versions close to as old as 1.7.2 which was released in 2010.

Change-Id: Ib71b714de6cd0b7dd50d0b300b108a560ee27331
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253134
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2020-02-05 19:01:40 +00:00
cc960971f4 sync: add option to skip manifest update
The use case is any situation where your manifest does
not exist on server, but where you still want to do
full sync for the projects, without having your
workspace manifest switched to other branch or
forwarded to latest or similar.
This allows syncing to a historical manifest in git log,
that does not have a branch, as well as when integrating
something together that has not been pushed upstream yet.
Changes can also exist locally on a manifest that is
behind head, meaning not requiring rebase to latest.

Tested using:
  $ cd .repo/manifests/
  $ git checkout <any hash 1>
  $ <do local modifications>
  $ repo sync --no-manifest-update
  $ git checkout <any hash 2>
  $ repo sync --no-manifest-update

Change-Id: I0c9773aa8bc5876813a2e7d7fec697abcb2d9e94
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/246445
Tested-by: Fredrik de Groot <fredrik.de.groot@volvocars.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-05 18:57:58 +00:00
75c02fe4cb init: handle -c conflicts with gitc-init
We keep getting requests for init to support -c.  This conflicts with
gitc-init which allocates -c for its own use.  Lets make this dynamic
so we keep it with "init" but omit it for "gitc-init".

Bug: https://crbug.com/gerrit/10200
Change-Id: Ibf69c2bbeff638e28e63cb08926fea0c622258db
(cherry picked from commit 66098f707a)
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253392
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-05 18:04:11 +00:00
66098f707a init: handle -c conflicts with gitc-init
We keep getting requests for init to support -c.  This conflicts with
gitc-init which allocates -c for its own use.  Lets make this dynamic
so we keep it with "init" but omit it for "gitc-init".

Bug: https://crbug.com/gerrit/10200
Change-Id: Ibf69c2bbeff638e28e63cb08926fea0c622258db
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253252
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-05 16:00:10 +00:00
f7b64e3350 Do not try to fetch default revision for mirrors always
* Mirrors may contain multiple projects, some of which may not
  always contain the default revision.
* Only fetch the default revision explicitly if
  '--current-branch' is set.
* Fixes breakage casued by
  commit 6856f98467
  "Fix repo mirror with --current-branch"

Bug: https://crbug.com/gerrit/12274
Change-Id: Iaafabe2992f76f3644b841f24245d3e19c9515a9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253093
Reviewed-by: Kuang-che Wu <kcwu@chromium.org>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Chirayu Desai <chirayudesai1@gmail.com>
2020-02-05 15:51:18 +00:00
afd1b4023f repo: point default branch to repo-1
Since this will be feature-frozen for Python 2 users, lets point the
default update branch to "repo-1" rather than "stable" as the latter
will follow the master development (and Python 3-only).

Bug: https://crbug.com/gerrit/10418
Change-Id: Iceff0983684a580dc5c9ec1c60acfb5eda5ce2c4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253172
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-05 02:56:08 +00:00
bd0aae95f5 Add a way to override the remote using <extend-project>
This commit supports for the 'remote' attribute in
<extend-project>. This avoids the need to perform a <remove-project>
followed by a <project> in local manifests.

Change-Id: I9f9347913337ec9d159bc264d15ce97881ae5398
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253092
Tested-by: Kyunam Jo <kyunam.jo@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-02-04 22:42:28 +00:00
e6a202f790 project: add basic path checks for <copyfile> & <linkfile>
Reject paths in <copyfile> & <linkfile> that try to use symlinks or
non-file or non-dirs.

We don't fully validate <linkfile> when src is a glob as it's a bit
complicated -- any component in the src could be the glob.  We make
sure the destination is a directory, and that any paths in that dir
are created as symlinks.  So while this can be used to read any path,
it can't be abused to write to any paths.

Bug: https://crbug.com/gerrit/11218
Change-Id: I68b6d789b5ca4e43f569e75e8b293b3e13d3224b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233074
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
2020-02-04 20:34:23 +00:00
04122b7261 manifest: add basic path checks for <copyfile> & <linkfile>
Reject paths in <copyfile> & <linkfile> that point outside of their
respective scopes.  This validates paths while parsing the manifest
as this should be quick & cheap: we don't access the filesystem as
this code runs before we've synced.

Bug: https://crbug.com/gerrit/11218
Change-Id: I8e17bb91f3f5b905a9d76391b29fbab4cb77aa58
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/232932
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
2020-02-04 20:34:01 +00:00
f5525fb310 repo: drop old signing key
This hasn't been used in many years to sign a release, so drop it
from the keyring to avoid confusing people.

Bug: https://crbug.com/gerrit/12229
Change-Id: Ifca7eee713d167c11f32252975724e5858e4c007
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253133
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-04 06:39:42 +00:00
ee451f035d repo: bump launcher version to 2.0
This reflects the transition to the new 2.x series which will be
migrating to Python 3-only.

Bug: https://crbug.com/gerrit/10418
Change-Id: I6355ac955d26b930f8a3721d3526eec5bed92400
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253132
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-04 06:15:26 +00:00
91d9587e45 Revert "Port _FileDescriptorStreamsNonBlocking to use poll()"
This reverts commit 1e01a74445.

Not all platforms support select.poll() currently it seems.
At least macOS's Python 2 doesn't (while macOS Python 3 does).

Lets back this out for the existing release series and once we
start repo-2 which is Python 3-only, we can put this back in.

Change-Id: I205206b0fa4fe2d755f4fbc6ec683ad125f27cc2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253072
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-02-03 23:01:07 +00:00
0bcc2d28d4 Fix docstring of project.Project.PrintWorkTreeStatus()
Change-Id: I1a9139d2ea3b3331a6f3ad3cae9e0ac37074d716
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/251837
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Rostislav Krasny <rostigm@gmail.com>
2020-01-25 13:29:56 +00:00
ec0ba2777f Fix method signature of platform_utils.FileDescriptorStreams._create_stream()
Change-Id: Ib80e4ec5e540d97488e7564703ddbcb74350fdfd
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/251836
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Rostislav Krasny <rostigm@gmail.com>
2020-01-25 13:29:08 +00:00
9da67feecf Fix a typo
Change-Id: I1d1d1c7ec6c0c706eb08ceb803c37e1ce1baf8b3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/251834
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Rostislav Krasny <rostigm@gmail.com>
2020-01-25 00:53:39 +00:00
b0b164a87f Add PyCharm project directory into the .gitignore
Change-Id: I9a785a9d045e44c6ec8bd4bd8d0169a81d5ccfde
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/251835
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Rostislav Krasny <rostigm@gmail.com>
2020-01-24 23:09:49 +00:00
b71d61d34e Make _preserve_encoding in platform_utils_win32 compatible with Python 3
Bug: https://crbug.com/gerrit/12145
Change-Id: I01d1ef96ff7b474f55ed42ecc13bd5943006d3b5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/251833
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Rostislav Krasny <rostigm@gmail.com>
2020-01-24 21:50:22 +00:00
8f997b38cb repo: Do not even try to set up GPG with opt.no_repo_verify
In order to be able to use "--no-repo-verify" to work around an issue with
gpg-agent and long socket paths (see e.g. [1]), this change avoids GPG
being set up at all if that option is passed.

[1] https://github.com/elastic/elasticsearch/issues/17053

Change-Id: I1e5cbd8be2dc0084f12afe0ca33c789fdbc6fef9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/251108
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2020-01-24 16:10:51 +00:00
0eb2d3c8a0 init: Add '-c' as an alias to '--current-branch'
This makes it consistent with the short option for current-branch in
repo sync.

Change-Id: I2848e87f45a66ef8d829576d0c0c4c0f7a8636a0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/241700
Tested-by: Diogo Ferreira <deovferreira@gmail.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-01-24 14:46:58 +00:00
e4d20372b2 info: Add the manifest revision
After Ib546f5ebbc8a23875fbd14bf166fbe95b7dd244e, repo info now displays
the current project revision in the 'Current revision' field.

While the output is more consistent, there are use cases for the
revision expression as shown in the manifest. This patch re-adds the
manifest revision as a new 'Manifest revision' field.

Change-Id: I50c1559dcb7ceb69af07352b956d78f85b8f592e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/240799
Tested-by: Diogo Ferreira <deovferreira@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2020-01-24 14:46:30 +00:00
1e01a74445 Port _FileDescriptorStreamsNonBlocking to use poll()
select() has a limit of FD_SETSIZE file descriptors. If you run repo
sync -j500 you'll pretty quickly hit this limit and get "file descriptor
out of range for select" errors. poll() has no such limit.

Change-Id: I21f350e472bda1db03dcbcc437645c23dbc7a901
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/248852
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Theodore Dubois <tbodt@google.com>
2019-12-18 21:16:23 +00:00
7c321f1bf6 repo: include subcommands in --help output
Also point people to `repo help` so it's easier to navigate the tool.

Bug: https://crbug.com/gerrit/12022
Change-Id: Ib3be331a2cef32caa193640bf8d54bd1443fce60
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/247292
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-12-05 05:00:21 +00:00
7ac12a9b22 docs: add Windows support info
Change-Id: I82a1bec3a29d622c76b5709b96bbe8bff8aa427f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/247573
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-12-05 01:02:08 +00:00
0b304c06ff help: unify command display
No functional changes, just unifying duplicate code paths.

Change-Id: I6afa797ca1e1eb90abdc0236325003ae070cbfb3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/247293
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-12-03 02:31:05 +00:00
4997d1c838 tox: add & document tox usage
This makes it easy to run all the tests against multiple versions
of Python.  We want to make sure Python 2.7 & 3.6+ work.

Change-Id: Ia7b16eb46a2aa7c240f03bb291987fa8cb215267
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/247174
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-12-02 04:24:23 +00:00
5b3a57c3ff setup.py: add basic packaging files
This is needed to use tox, and tox lets us test multiple Python
versions easily.

Change-Id: I813c418a8f7109294a4adb9f6b21be459cbeca70
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/247173
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-12-02 04:23:31 +00:00
6f8c85ce2a run_tests: improve exit code behavior
Rather than throw an exception when pytest itself exits non-zero,
pass that back up.  The traceback is never useful, only confusing.

Change-Id: I0cd7bea730e13c9969154326057196295e550843
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/247175
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-12-02 04:20:10 +00:00
6856f98467 Fix repo mirror with --current-branch
Before a2cd6aeae8, "repo mirror with --current-branch" fetches git data
using command
    git fetch --progress --update-head-ok cros --tags
No refspec is specified, thus it fetches default refspec, which is +refs/heads/*:refs/heads/*

After a2cd6aeae8, the fetch command became
     git fetch --progress --update-head-ok cros --tags +refs/tags/*:refs/tags/*
It did not only add tags refspec, but also suppressed the fetching of default refspec.

In other words, repo mirrors doesn't work if current_branch_only=True.
This CL explicitly adds the default refspec to command line if none is
specified.

Bug: https://crbug.com/gerrit/11990
Change-Id: Iadcf7b9aa50f53c47132cfe6c53b3fb2076ebca2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/246632
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Kuang-che Wu <kcwu@chromium.org>
2019-11-25 20:12:34 +00:00
34bc5712eb README: add install details
Change-Id: I57043449a7927068fa5735cb71633353e1039532
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/245816
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-11-18 19:17:48 +00:00
70c54dc255 upload/editor: fix bytes/string confusion
The upload module tries to turn the strings into bytes before passing
to EditString, but it combines bytes & strings causing an error.  The
return value might be bytes or string, but the caller only expects a
string.  Lets simplify this by sticking to strings everywhere and have
EditString take care of converting to/from bytes when reading/writing
the underlying files.  This also avoids possible locale confusion when
reading the file by forcing UTF-8 everywhere.

Bug: https://crbug.com/gerrit/11929
Change-Id: I07b146170c5e8b5b0500a2c79e4213cd12140a96
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/245621
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-11-16 23:55:30 +00:00
6da17751ca prune: handle branches that track missing branches
Series of steps:
* Create a local "b1" branch with `repo start b1` that tracks a remote
  branch (totally fine)
* Manually create a local "b2" branch with `git branch --track b1 b2`
  that tracks the local "b1" (uh-oh...)
* Delete the local "b1" branch manually or via `repo prune` (....)
* Try to process the "b2" branch with `repo prune`

Since b2 tracks a branch that no longer exists, everything blows up
at this point as we try to probe the non-existent ref.  Instead, we
should flag this as unknown and leave it up to the user to resolve.

This probably could come up if a local branch was tracking a remote
branch that was deleted from the server, and users ran something like
`repo sync --prune` which cleaned up the remote refs.

Bug: https://crbug.com/gerrit/11485
Change-Id: I6b6b6041943944b8efa6e2ad0b8b10f13a75a5c2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/236793
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Kirtika Ruchandani <kirtika@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2019-11-16 19:55:02 +00:00
2ba5a1e963 sync: try to fast forward merge branches before checking published state
If the local branch changed state since its last upload, the data
cached in .git/config related to the last uploaded CL might not be
that relevant.  If we're able to fast forward merge to the latest
tree state, then let's do that.  This would be akin to checking
out a detached head before syncing where we already switch state.

If we aren't able to fast forward merge, then it's not a big deal
as we'll continue on to the existing branch checking logic.

This would be easy to reproduce by doing something like:
  $ repo start foo .
  $ git revert HEAD
  $ repo upload --cbr .
  $ git reset --hard HEAD^
  <CL is merged>
  $ repo sync .
  <we can fast forward>

Change-Id: I7d62f3d1ba5314a349d85b4dbb0ec8352eca18bb
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/238552
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2019-11-13 20:29:33 +00:00
3538dd224d sync: merge project updates with status bar
The current sync output displays "Fetching project" and "Checking out
project" messages and progress bar updates independently leading to a
lot of spam.  Lets merge these periodic outputs with the status bar to
get a little bit tighter output in the normal case.  This doesn't solve
all our problems, but gets us closer.

Bug: https://crbug.com/gerrit/11293
Change-Id: Icd627830af4dd934a9355b7ace754b56dc96cfef
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/244934
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-11-12 23:33:51 +00:00
b610b850ac sync: add sanity check for local checkouts missing network
If you run `repo sync -l foo` without first `repo sync -n foo`,
repo sets up an invalid gitdir tree that gets wedged and requires
manual recovery.  Add a sanity check to abort cleanly first.

Change-Id: Iad865ea860a3f1fd2f39ce683fe66bd4380745a5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/244732
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-11-12 23:14:28 +00:00
dff919493a sync: report list of failing git trees
When repo sync fails because some git trees are not in clean state and
as such can not be rebased automatically, it is a pain to figure out
which trees are the culprits.

With this patch the list of offending trees is printed when repo sync
reports checkout errors.

TEST=ran 'repo sync' and observed the proper list of directories show
     up after the final error message

Bug: https://crbug.com/gerrit/11293
Change-Id: Icdf1a03e9014ecb184f331f513cc9a2efc7d11ed
Signed-off-by: Vadim Bendebury <vbendeb@google.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/244053
Reviewed-by: Mike Frysinger <vapier@google.com>
2019-11-12 21:08:54 +00:00
3164d40e22 use open context managers in more places
Use open() as a context manager to simplify the close logic and make
the code easier to read & understand.  This is also more Pythonic.

Change-Id: I579d03cca86f99b2c6c6a1f557f6e5704e2515a7
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/244734
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-11-12 03:44:39 +00:00
f454512619 sync: make .git init more robust
Hitting Ctrl-C in the middle of this func will leave the .git in a
bad state that requires manual recovery.  The code tries to catch
all exceptions and recover by deleting the incomplete .git dir, but
it omits KeyboardInterrupt which Exception misses.

We could add that to the recovery path, but we can make this more
robust with a different approach: set up everything in .git.tmp/
and only move it to .git/ once we've fully initialized it.

Change-Id: I0f5b97f2e19fc39cffc3e5e23993a2da7220f4e3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/244733
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-11-12 03:44:33 +00:00
b466854bed forall: add an --ignore-missing option
In CrOS, our infra has to deal with partial checkouts constantly
(for a variety of reasons).  To help reset back to a good state,
we run git commands via `repo forall`, but don't care about the
missing checkouts.  Add a flag so we can disambiguate between
missing repos and failing git subcommands.

Bug: https://crbug.com/1013377
Bug: https://crbug.com/1013623
Change-Id: Ie3498c6d111276c60d2ecedbba21bfa778588d50
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/241935
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-22 14:10:34 +00:00
d1e93dd58a python-support: adjust major versions
The plan previously documented was <=1.13.x is Python 2 and >=1.14.x
is Python 3.  Other projects that migrated Python versions and drop
support for older have tended to take a more drastic version jump to
make it clearer to users.  So lets adjust the plan to say <=1.x will
support Python 2, and >=2.x will be Python 3-only.

This also allows us to harmonize the repo launcher version.  It is
currently sitting at v1.26 and has been incremented independently of
the repo version for the life of the project.  While we might know
these lower nuances, pretty much no one else does and it just leads
to confusion: do I know version 1.26 or version 1.13.7?  Or do I
have both?  What does that even mean?

Once we update the major version to 2.0.0, we can also adjust the
launcher script to 2.0.0, and then the launcher release process will
be tied to a new repo release in general.

Bug: https://crbug.com/gerrit/10418
Change-Id: Idb2257371a06e56d2923cf717345c028f49176a2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/240372
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-11 06:56:03 +00:00
e778e57f11 command: filter projects by active manifest groups
`repo forall <proj>` will look up all <proj> in the manifest for all
manifest groups regardless of which are active.  If <proj> is checked
out to different locations depending on the group, this ultimately
fails as we're unable to locate all of them.

Simple fix is to only include projects that match the manifest groups
that we already passed down & initialized to the active set, and that
we already use when getting the default project list.

Bug: https://crbug.com/gerrit/11677
Bug: https://crbug.com/1011226
Change-Id: I975f10f9a9e5a1cad7d87344123f8003732dab27
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239652
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Raul Rangel <rrangel@chromium.org>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-10-08 20:15:08 +00:00
f1c5dd8a0f info: fix "current" output
The "Current revision" field shows the revision as listed in the
manifest.  I think most users expect this to show the revision
that the git tree is checked out to instead.  Switch the output
to show that revision instead, and add a "Current branch" if it
matches a local branch.

Change-Id: Ib546f5ebbc8a23875fbd14bf166fbe95b7dd244e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239240
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-05 05:24:34 +00:00
2058c63641 Only import imp on py2
imp is deprecatedon py3. It's also not used with py3, so just move it
to the py2 import block

Test: run `repo` command and verify warning is no longer present
Test: verify `repo sync` and `repo upload` function as expected
Change-Id: I9d59403d7819c4a478c9f54cbef114f8a96486a5
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239713
Tested-by: Rashed Abdel-Tawab <rashedabdeltawab@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
2019-10-05 04:41:40 +00:00
c8290ad49e project: allow CurrentBranch to return None on errors
If the repo client checkout is in an incomplete sync state, the work
git repo might be in a bad way.  Turn errors parsing HEAD into None
since callers of CurrentBranch already need to account for it.

Change-Id: Ia7682e29ef4182006b1fb5f5e57800f8ab67a9f4
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239239
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-01 05:53:35 +00:00
9775a3d5d2 info: allow NoSuchProjectError to bubble up
If the user passes in bad projects like `repo info asdf`, we currently
silently swallow those and do nothing.  Allow NoSuchProjectError to
bubble up to main which will handle & triage this correctly for us.

Change-Id: Ie04528e7b7a164293063a636813a73eaabdd5bc3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239238
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-01 05:53:09 +00:00
9bfdfbe117 version: add source versions & User-Agents to the output
Depending on where/how repo is invoked, the active version might be
from a git tree, and it might be different from the .repo/repo.git/
version in the current repo client checkout.  Report both if they're
different so it's clearer.

Lets also include the two different User-Agent's that we set up when
talking to networked services.

Bug: https://crbug.com/gerrit/11144
Change-Id: I2ebb6e3ac30e374a8406cab3e4438087246a8c57
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239234
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-01 05:47:35 +00:00
2f0951b216 git_command: set GIT_HTTP_USER_AGENT on all requests
We've been setting the User-Agent header when making connections
from repo itself, but not when running git (as the latter will set
up User-Agent itself).  Our Gerrit/Git admins say it'll be helpful
if we pass through the repo version settings even when running git.

We currently set GIT_HTTP_USER_AGENT and not GIT_USER_AGENT as it's
unclear if the extended form works over all protocols.  We can wait
for a user request.

Bug: https://crbug.com/gerrit/11144
Change-Id: I21d293f49534058dbc23225152451df26c5b7bef
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239233
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-10-01 05:47:17 +00:00
72ab852ca5 grep: handle errors gracefully
If `git grep` fails in any project checkout (e.g. an incomplete
sync), make sure we print that error clearly rather than blowing
up, and exit non-zero in the process.

Bug: https://crbug.com/gerrit/11613
Change-Id: I31de1134fdcc7aaa9814cf2eb6a67d398eebf9cf
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239237
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-01 05:45:58 +00:00
0a9265e2d6 diff: handle errors gracefully
If `git diff` fails in any project checkout (e.g. an incomplete
sync), make sure we print that error clearly rather than blowing
up, and exit non-zero in the process.

Bug: https://crbug.com/gerrit/11613
Change-Id: I12f278427cced20f23f8047e7e3dba8f442ee25e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239236
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-01 05:44:09 +00:00
dc1b59d2c0 forall: exit 1 if we skip any repos
If a repo doesn't exist (e.g. an incomplete sync), make sure we exit
non-zero when they get skipped.

Change-Id: Ifff711e374416b1e6b9b8da4fdc6f14b27ced450
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239235
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-01 05:43:51 +00:00
71b0f312b1 git_command: refactor User-Agent settings
Convert the RepoUserAgent function into a UserAgent class.  This
makes it cleaner to hold internal state, and will make it easier
to add a separate git User-Agent, although we don't do it here.

We make the RepoSourceVersion independent of GitCommand so that
it can be called by the class (later).

Bug: https://crbug.com/gerrit/11144
Change-Id: Iab4e1f974b8733a36b243b2d03f5085a96effa19
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/239232
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-10-01 05:40:28 +00:00
369814b4a7 move UserAgent to git_command for wider user
We can't import the main module, so move the UserAgent helper out of
it and into the git_command module so it can be used in more places.

Bug: https://crbug.com/gerrit/11144
Change-Id: I8093c8a20bd1dc7d612d0e2a85180341817c0d86
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/231057
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-10-01 05:39:27 +00:00
e37aa5f331 rebase: add basic coloring output
This uses coloring style like we use in grep/forall already.

Change-Id: I317e2e47567a30c513083c48e7c7c40b091bb29a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/238555
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-09-24 04:17:03 +00:00
4a07798c82 rebase: add --fail-fast support
Lets switch the default rebase behavior to align with our new sync
behavior: we try to rebase all projects by default and exit/summarize
things at the very end if there were any errors.  Or if people want
to exit immediately, they can use the new --fail-fast option.

Change-Id: I436ac563f972b45de6ce9ad74da1e4870e584902
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/238553
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-09-24 02:06:26 +00:00
fb527e3f52 sync: create dedicated manifest project update func
Cut out some more standalone code from Execute to make this func a
bit more manageable.  The manifest project update is pretty simple
and standalone, but still takes up a good chunk of what's left.

Change-Id: Idc2442d9def495eccd0a49cda203c44aef16f129
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/236614
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-09-19 01:52:08 +00:00
6be76337a0 repo: bump wrapper version
We've rolled quite a number of fixes since the last update, including
a lot of Python 3 improvements.  Lets bump the wrapper version for it.

Change-Id: I6c6c04c3c8241bf8e8bcf26603549ae4595fede8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/237812
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-09-18 08:25:49 +00:00
a2cd6aeae8 Fix tag clobbering when -c is used.
Bug: b/140189154
Change-Id: I8861a6115b20c9a3d88ddec5344c75326ae44823
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/237572
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Xin Li <delphij@google.com>
2019-09-16 18:34:45 +00:00
70d861fa29 sync: improve output with intermingled progress bars and status
When displaying progress bars, we use \r to reset the cursor to the
start of the line before showing the new update.  This assumes the
new line will fully erase whatever was displayed there previously.
The "done" codepath tries to handle this by including a few extra
spaces at the end of the message to "white out" what was there.

Lets replace that hack with the standard ECMA escape sequence that
clears the current line completely.  This is the CSI "erase in line"
sequence that the terminal will use to delete all content.  The \r
is still needed to move the cursor to the start of the line.  Using
this sequence should be OK since we're already assuming the terminal
is ECMA compliant with our use of coloring sequences.  We also put
the \r after the CSI sequence on the off chance the terminal can't
process it and displays a few bytes of garbage.

The other improvement is to the syncbuffer API.  When it dumps its
status information, it almost always comes after a progress bar
update which leads to confusing comingled output.  Something like:
  Fetching projects: 100% (2/2) error: src/platform2/: branch ...
Since the progress bar is "throw away", have the syncbuffer reset
the current output to the start of the line before showing whatever
messages it has queued.

Bug: https://crbug.com/gerrit/11293
Change-Id: I6544d073fe993d98ee7e91fca5e501ba5fecfe4c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/236615
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-09-13 02:58:07 +00:00
9100f7fadd repo: decode/encode all the subprocess streams
We use subprocess a lot in the wrapper, but we don't always read
or write the streams directly.  When we do, make sure we convert
to/from bytes before trying to use the content.

Change-Id: I318bcc8e7427998348e359f60c3b49e151ffbdae
Reported-by: Michael Scott <mike@foundries.io>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/236612
Reviewed-by: Michael Scott <mike@foundries.io>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Michael Scott <mike@foundries.io>
2019-09-12 04:16:51 +00:00
01d6c3c0c5 sync: create dedicated smart sync func
The smart sync logic takes up about 45% of the overall Execute func
and is about 100 lines of code.  The only effect it has on the rest
of the code is to set the manifest_name variable.  Since this func
is already quite huge, split the smart sync logic out.

Change-Id: Id861849b0011ab47387d74e92c2ac15afcc938ba
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/234835
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-09-11 23:24:11 +00:00
4c263b52e7 repo: fix unused variable usage
The refactoring here left behind a variable reference that no
longer exists.  Clean it up.

Bug: https://crbug.com/gerrit/11144
Change-Id: Ifdb7918b37864c48f3deef27c8bae3f793275d35
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/236613
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-09-11 17:57:49 +00:00
60fdc5cad1 Add repo start option to create the branch based off HEAD
This makes it way easier to recover from forgetting to run repo start
before committing: just run `repo start -b new-branch`, instead of
all that tedious mucking around with reflogs.

Change-Id: I56d49dce5d027e28fbba0507ac10cd763ccfc36d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/232712
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-09-04 04:34:50 +00:00
46702eddc7 sync: fix deprecated command line option -f
In commit d9e5cf0e ("sync: invert --force-broken with --fail-fast") the
force-broken option has been deprecated. Accidentally the option has
been changed from Boolean to Value. This breaks all users of repo with:

  main.py: error: -f option requires an argument

This is easy to avoid by keeping the type.

Signed-off-by: Stefan Müller-Klieser <s.mueller-klieser@phytec.de>
Change-Id: Ia8b589cf41ac756d10c61e17ec8d76ba8f7031f9
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/235043
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-30 16:29:01 +00:00
ae6cb08ae5 split out cli validation from execution
A common pattern in our subcommands is to verify the arguments &
options before executing things.  For some subcommands, that check
stage is quite long which makes the execution function even bigger.
Lets split that logic out of the execute phase so it's easier to
manage these.

This is most noticeable in the sync subcommand whose Execute func
is quite large, and the option checking makes up ~15% of it.

The manifest command's Execute can be simplified significantly as
the optparse configuration always sets output_file to a string.

Change-Id: I7097847ff040e831345e63de6b467ee17609990e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/234834
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-28 03:54:11 +00:00
3fc157285c add a --trace-python option
This can help debug issues by tracing all the repo python code with
the standard trace module.

Change-Id: Ibb7f4496ab6c7f9e130238ddf3a07c831952697a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/234833
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-08-27 18:44:17 +00:00
8a11f6f24c rename local trace module
There is a standard Python "trace" module, so having a local trace.py
prevents us being able to import that.  Rename the module to avoid.

Change-Id: I23e29ec95a2204bb168a641323d05e76968d9b57
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/234832
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-27 07:08:52 +00:00
898f4e6217 help: add a --help-all option to show all commands at once
This is useful when you want to scan all the possibilities of repo
at once.  Like when you're searching for different option names.

Change-Id: I225dfb94d2be78229905b744ecf57eb2829bb52d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/232894
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-27 07:07:24 +00:00
d9e5cf0ee7 sync: invert --force-broken with --fail-fast
People seem to not expect the sync process to halt immediately if an
error is encountered.  It's also basically guaranteed to leave their
tree in an incomplete state.  Lets invert the default behavior so we
attempt to sync (both fetch & checkout) all projects.  If an error is
hit, we still exit(1) and show it at the end.

If people want the sync to abort quickly, they can use the new option
--fail-fast.

Bug: https://crbug.com/gerrit/11293
Change-Id: I49dd6c4dc8fd5cce8aa905ee169ff3cbe230eb3d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/234812
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-27 01:20:44 +00:00
3069be2684 Explicitly allow clobbering tags when fetching from remote.
Bug: b/139860049
Change-Id: I3c4134eda7e9e75c9d72b233e269bcc0e624d1e8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/234632
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Xin Li <delphij@google.com>
2019-08-22 18:33:41 +00:00
d5c306b404 rebase: pull out project-independent settings from the for loop
This makes the code a bit easier to read by doing all the project
independent settings first instead of repeating it for every for
loop iteration.

Change-Id: I4ff21296e444627beba2f4b86561069f5e9a0d73
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233554
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-08 17:41:40 +00:00
a850ca2712 rebase/sync: use exit(1) for errors instead of exit(-1)
Callers don't actually see -1 (they'll usually see 255, but the exact
answer here is complicated).  Just switch to 1 as that's the standard
value tools use to indicate an error.

Change-Id: Ib712db1924bc3e5f7920bafd7bb5fb61f3bda44f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233553
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-08 05:07:31 +00:00
a34186e481 sync: fix handling of -f and local checkouts
The partial clone rework (commit 745be2ede1
"Add support for partial clone") changed the behavior when a single repo
hit a failure: it would always call sys.exit() immediately.  This isn't
even necessary as we already pass down an error event object which the
workers set and the parent checks.  Just delete the exit entirely.

Change-Id: Id72d8642aefa2bde24e1a438dbe102c3e3cabf48
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233552
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-08 02:54:39 +00:00
600f49278a project: fix encoding handling with git commands
The GitCommand Wait helper takes care of decoding bytes to strings
for us.  That means we don't have to decode stdout ourselves which
is what our local rev list, ls-remote, and generic get_attr helpers
were doing.

If we don't use Wait though to capture the output but instead go
directly to the subprocess stdout, we do have to handle decoding
ourselves.  This is what the diff helpers were doing.

Bug: https://crbug.com/gerrit/10418
Change-Id: I057ca245af3ff18d6b4a074e3900887f06a5617d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233076
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-04 04:13:55 +00:00
1f2462e0d2 git_config: include project name in missing ref exception
When syncing in parallel, this exception is hard to trace back to
a specific repo as the relevant log line could have been pushed
out by other repos syncing code.

Change-Id: I382efeec7651e85622aa51e351134aef0148267f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233075
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: Nasser Grainawi <nasser@codeaurora.org>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-03 16:15:48 +00:00
50d27639b5 manifest-format: document implicit directory creation w/<copyfile> & <linkfile>
Bug: https://crbug.com/gerrit/11218
Change-Id: Ie96b4c484d9fbfd550c580c3d02971dc088dd8b0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/233052
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Jonathan Nieder <jrn@google.com>
2019-08-02 04:21:40 +00:00
c5b172ad6f manifest-format: clarify <copyfile> & <linkfile> restrictions
While we don't (yet) explicitly enforce all of these, make sure
we document the expected behavior so we can all agree on it.

Bug: https://crbug.com/gerrit/11218
Change-Id: Ife8298702fa445ac055ef43c6d62706a9cb199ce
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/232893
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-08-01 04:06:04 +00:00
87deaefd86 tests: add docstrings & print_function (for Python 3)
Bug: https://crbug.com/gerrit/10418
Change-Id: Id98183597a9b0201ca98ec0bf5033a5f5ac6bda2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/232892
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-08-01 03:03:48 +00:00
5fbd1c6053 wrapper: Fix indentation level
Change-Id: I6bee1771053fd8da9c135ed529c4926b42ee9f87
Signed-off-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/232792
Reviewed-by: Jonathan Nieder <jrn@google.com>
2019-07-31 08:38:19 +00:00
1126c4ed86 wrapper: replace usage of deprecated imp module for Python 3
A warning is emitted

  DeprecationWarning: the imp module is deprecated in favour of
  importlib; see the module's documentation for alternative uses

Change-Id: I6c5a9e024a9a904e02a24331f615548be3fe5f8e
Signed-off-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/230984
Reviewed-by: Jonathan Nieder <jrn@google.com>
2019-07-31 00:55:37 +00:00
f7c51606f0 hooks: support external hooks running different Python version
As we convert repo to support Python 3, the version of Python that we
use might not be the version that repo hooks users have written for.
Since repo upgrades are not immediate, and not easily under direct
control of end users (relative to the projects maintaining the hook
code), allow hook authors to declare the version of Python that they
want to use.

Now repo will read the shebang from the hook script and compare it
against the version of Python repo itself is running under.  If they
differ, we'll try to execute a separate instance of Python and have
it load & execute the hook.  If things are compatible, then we still
use the inprocess execution logic that we have today.

This allows repo hook users to upgrade on their own schedule (they
could even upgrade to Python 3 ahead of us) without having to worry
about their supported version being exactly in sync with repo's.

Bug: https://crbug.com/gerrit/10418
Change-Id: I97c7c96b64fb2ee465c39b90e9bdcc76394a146a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/228432
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-07-27 01:10:40 +00:00
745be2ede1 Add support for partial clone.
A new option, --partial-clone is added to 'repo init' which tells repo
to utilize git's partial clone functionality, which reduces disk and
bandwidth usage when downloading by omitting blob downloads initially.
Different from restricting clone-depth, the user will have full access
to change history, etc., as the objects are downloaded on demand.

Change-Id: I60326744875eac16521a007bd7d5481112a98749
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/229532
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Xin Li <delphij@google.com>
2019-07-16 00:23:16 +00:00
87fb5a1894 repo/main: add module docstrings
This should help people get some bearings in the codebase.

Change-Id: I951238fe617a3ecb04a47ead3809ec72c8fbf5a1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/231232
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-07-12 17:23:53 +00:00
ab85fe7c53 use print() instead of sys.stdout.write()
We're relying on sys.stdout.write() to flush its buffer which isn't
guaranteed, and is not the case in Python 3.  Change to use print()
everywhere to be standard, and utilize the end= keyword to get the
EOL semantics we need.

We can't use print's flush= keyword as that's only in Python 3.
Leave behind a TODO to clean it up when we can drop Python 2.

Bug: https://crbug.com/gerrit/10418
Change-Id: I562128c7f1e6d154f4a6ecdf33a70fa2811dc2af
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/230392
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-07-11 06:26:40 +00:00
4f42a97067 run_tests: add a helper for invoking unittests
This makes it very easy for people to run all our unittests with just
`./run_tests`.  There doesn't seem to be any other way currently to
quickly invoke any of the tests.

Change-Id: I1f9a3745fa397a1e797bd64065c2ba7f338de4a1
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/227613
Tested-by: David Pursehouse <dpursehouse@collab.net>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-07-11 04:34:08 +00:00
2b7daff8cb Don't try to decode when checking clone bundles
This fix exception with python3 with stack-trace:

error: Cannot fetch platform_external_grpc-grpc-java.git (UnicodeDecodeError: 'utf-8' codec can't decode byte 0x96 in position 640: invalid start byte)

[...]
  File "[...]project.py", line 2255, in _IsValidBundle
    if f.read(16) == '# v2 git bundle\n':
  File "/usr/lib/python3.5/codecs.py", line 321, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)

Even if we ask 16 characters, python buffered decoder will try to decode more in the buffer

The patch works for python2 and python3, and open the file in byte mode so that decoding is not attemped

Signed-off-by: Pierre Tardy <tardyp@gmail.com>
Change-Id: I837ae3c5cd724b34670fc2a84e853431f482b20d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/224642
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-07-11 01:33:37 +00:00
242fcdd93b main: user-agent: include full git version info
We've been truncating the git version info in the user agent to the
first three components.  So given an example `git --version` like
"2.22.0.510.g264f2c817a-goog", we were cutting it down to "2.22.0".
For user-agent purposes, we usually want that full string, so use
the original full value instead.

Bug: https://crbug.com/gerrit/11144
Change-Id: I8ffe3186bdaac96164c34ac835a54bb3fc85527e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/231056
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-07-11 01:32:38 +00:00
ca540aed19 git_command: drop custom version helper
Since ParseGitVersion can call `git --version` automatically, we don't
need this duplicate version() helper anymore.  The only other user is
the `repo version` code, so convert that to version_tuple().full.

Bug: https://crbug.com/gerrit/11144
Change-Id: I9d77822fc39f4ba28884d9183359169cabf5f17d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/231055
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-07-11 01:30:18 +00:00
f88b2fe569 repo: all ParseGitVersion to load git version info itself
All code that calls ParseGitVersion needs to run `git --version`
itself and parse the output before passing it in.  To avoid that
duplication, allow ParseGitVersion to run `git --version` itself
if ver_str=None.

Bug: https://crbug.com/gerrit/11144
Change-Id: Ie07793ca57a40c0231af808df04a576118d5eea3
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/231054
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
2019-07-11 01:29:18 +00:00
6db1b9e282 repo: return a namedtuple with full version info
We were returning an e.g. tuple(1,2,3), but that strips off the full
version string which we might want in some places e.g. '1.2.3-rc3'.
Change the return value to a namedtuple so we can pass back up the
full version string.  For code doing a compare with three elements
(all code today), things still work fine as the namedtuple will DTRT
in this scenario.

Bug: https://crbug.com/gerrit/11144
Change-Id: Ib897b5df308116ad1550b0cf18f49afeb662423e
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/231053
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Tested-by: Mike Frysinger <vapier@google.com>
2019-07-11 01:28:14 +00:00
490e16385d Remove double forall from "repo help forall" output
%prog represents the full subcommand ("repo" + subcommand name), not a
Windows-style environment variable for "repo". The current help output
shows

 repo forall% forall ...

Correct the variable usage so it shows "repo forall ..." instead.

Change-Id: I1fea55572428cc922ddf24ace1168a3d8f82dad0
2019-07-08 22:42:38 +00:00
ec558df074 fix raise syntax
This takes a single argument (the error message), not multiple
arguments that get formatted implicitly.

Change-Id: Idfbc913ea9f93820edb7e955e9e4f57618c8cd1b
2019-07-05 01:38:14 -04:00
81f5c59671 project: rev_list: simplify execution
Currently we read the binary stream from the subprocess code directly
before waiting for it to finish, but there's no need to do so as we
aren't streaming the output to the user.  This also means we pass up
binary data to the caller as we don't go through GitCommand's internal
logic which decodes the stream as utf-8.

Simplify the code by calling Wait first, then splitting the entire
captured output in one line.

Bug: https://crbug.com/gerrit/10418
Change-Id: I7a57904be8cb546a229980fb79c829fc3df31e7d
2019-07-05 05:31:38 +00:00
1b9adab75a handle binary stream from urllib.request.urlopen
Python 3 returns bytes by default with urlopen.  Adjust our code to
handle that scenario and decode as necessary.

Bug: https://crbug.com/gerrit/10418
Change-Id: Icf4cd80e7ef92d71a3eefbc6113f1ba11c32eebc
2019-07-04 18:19:00 -04:00
3698ab7c92 Support clone bundle through persistent-http[s].
Bug: https://crbug.com/gerrit/11075
Change-Id: I367c6bfe8da47d886c017a2ac614d4ccb3f8a438
2019-06-26 09:42:21 -07:00
0c0e934b69 sync: use integer division with job counts
Neither of the fields here expect floats so make sure we use integer
division when calculating things.

Bug: https://crbug.com/gerrit/10418
Change-Id: Ibda068b16a7bba7ff3efba442c4bbff4415caa6e
2019-06-14 14:47:01 +00:00
9e71842fbf status: import print_function
This module uses print() so make sure we import the print function.
It doesn't really impact the current code due to the simple way it
is calling print, but we should be sane to avoid future issues.

Change-Id: I0b15344678c1dcb71207faa333c239b3fced1d62
2019-06-14 14:13:23 +00:00
61b2d41f26 add license header to a few more files
Change-Id: I24e6b1df5f15a8e71c0f4a9edac505a8902ec267
2019-06-13 13:23:19 -04:00
da9e200f1d repo: drop Python 3 warning
Lets get people to start filing bugs :).

Bug: https://crbug.com/gerrit/10418
Change-Id: I1d55bf0c60dbdbd6537d30b2cf9ea91d2928e387
2019-06-13 14:32:03 +00:00
c92ce5c7dc repo: restore use of print_function
We avoided this future import because Python 2.4 & 2.5 did not
support it.  We've dropped support for Python 2.6 at this point,
and those versions are long dead.  Since this workaround adds a
bit of complexity to the codebase, drop it.  Considering we are
not running any actual tests against older versions, there's no
sense in trying to support them anymore.

Change-Id: Icda874861e8a8eb4fa07c624a9e7c5ee2a0da401
2019-06-13 14:31:45 +00:00
f601376e13 set default file encoding to utf-8
There's no reason to support any other encoding in these files.
This only affects the files themselves and not streams they open.

Bug: https://crbug.com/gerrit/10418
Change-Id: I053cb40cd3666ce5c8a0689b9dd938f24ca765bf
2019-06-13 14:30:52 +00:00
31067c0ac5 tweak raise/dict syntax for Python 3 compat
Use the `raise` statement directly.

Switch to using .items() instead of .iteritems().  Python 3 doesn't
have .iteritems() as .items() is a generator, and these are small
enough that the Python 2 overhead should be negligible.

We have to run .keys() through list() in a few places as Python 3
uses a generator and we sometimes want to iterate more than once.
That's why we don't change all .keys() or .items() calls -- most
are in places where generators are fine.

Bug: https://crbug.com/gerrit/10418
Change-Id: I469899d9b77ffd77ccabb831bc4b217407fefe6f
2019-06-13 13:39:25 +00:00
35159abbeb repo: standardize help behavior
Standard utilities exit normally/zero when users explicitly request
--help, and they write to stdout.  Exiting non-zero & using stderr
is meant for incorrect tool usage instead.  We're already doing this
for `repo help <init|gitc-init>` calls, so lets fix `repo help` and
`repo --help|-h` to match.

Change-Id: Ia4f352b431c91eefef70dcafc11f00209ee69809
2019-06-13 13:34:54 +00:00
24ee29e468 wrapper: drop shebang
This isn't executable (+x), nor does it have a main func or code
that would run if it were.  It's simply an imported module like
most others in here.  Drop the shebang to avoid confusion.

Change-Id: I5e2881eb1de5e809a3fa9e8f49220ed797034fb1
2019-06-13 02:00:17 -04:00
1b291fc2e7 Merge "docs: start a release document" 2019-06-13 05:57:59 +00:00
a26c49ead4 docs: start a release document
Change-Id: I884639665c020338ec9ceeb1add5c3b862583674
2019-06-12 23:18:10 -04:00
c745350ab9 diffmanifests: honor user-supplied manifest paths
The current implementation ignores the user-specified paths to
manifests.  if the "repo diffmanifests" is invoked with absolute
file paths for one or both manifests, the command fails with message:

fatal: duplicate path ... in /tmp/manifest-old.xml

Also the current implementation fails to expand the absolute path to
manifest files if "repo diffmanifests" is invoked with relative
paths, i.e "repo diffmanifests manifest-old.xml manifest-new.xml".

fatal: manifest manifest-old.xml not found

This commit fixes the first issue by disabling the local manifest
discovery for diffmanifests command, and the second issue by
expanding paths to manifests within "diffmanifests" sub-command.

Test: repo manifest --revision-as-HEAD -o /tmp/manifest-old.xml
      repo sync
      repo manifest --revision-as-HEAD -o /tmp/manifest-new.xml
      repo diffmanifests /tmp/manifest-old.xml /tmp/manifest-new.xml
Change-Id: Ia125d769bfbea75adb9aba81abbd8c636f2168d4
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2019-06-06 07:36:10 +00:00
025704e946 Merge "platform_utils_win32: remove an unnecessary workaround" 2019-06-04 16:28:15 +00:00
c5b0e23490 project: Set config option to skip lfs process filter
During sync, repo runs `git read-tree --reset -u -v HEAD` which causes
git-lfs's smudge filter to run, which fails because git-lfs does not
work with bare repositories.

This was fixed in I091ff37998131e2e6bbc59aa37ee352fe12d7fcd to
automatically disable this smudge filter. However, later versions of
Git (2.11.0) introduced a new filter protocol [1], to avoid spawning
a new command for each filtered file. This was implemented in Git-LFS
1.5.0 [2].

This patch fixes the issue by setting the git lfs process filter, in
addition to the smudge filter. For any projects that have LFS objects,
`git lfs pull` must still be executed manually afterwards.

[1] edcc85814c
[2] https://github.com/git-lfs/git-lfs/pull/1617

Bug: https://crbug.com/gerrit/10911
Change-Id: I277fc68fdefc91514a2412b3887e3be9106cab48
2019-05-24 12:06:33 +00:00
d92464e8ef Honor --depth during repo init
If a user is asking for a shallow clone of the repos, they probably
expect a shallow clone of the manifest repo too. For very large
manifest repos, this can be a huge space and time savings. For one real-world
repo, a 'repo init --no-tags --current-branch' used 350MB of disk space and
took 7 minutes. Adding --depth 1 and this change reduced it to 10MB and 2.5
minutes.

Change-Id: I6fa662e174e623ede8861efc862ce26d65d4958d
2019-05-21 10:47:21 -06:00
0968570df2 Merge "Print project name when work tree initialization fails" 2019-05-16 23:05:53 +00:00
f25a370a14 Use %topic=topic instead of deprecated /topic syntax on push
Bug: https://crbug.com/gerrit/9930
Change-Id: Iefa202d42ef6e6b8b2b1a3f9b8baa5f0d65cbd60
2019-05-15 10:59:53 +02:00
b554838ce8 Print project name when work tree initialization fails
When syncing a lot of projects in parallel, it is not otherwise
clear which one of them has failed to init work tree.

Change-Id: I8edfb4955023389a499e99cfa511bdc0d2850ba2
2019-05-09 13:07:20 -07:00
2d095da4f1 Ignore submodules when calculating 'git diff-files'
This allows projects to include submodules inside of
projects that use repo without repo incorrectly believing
the area is dirty just because a submodule has updates.
This is in line with git porcelain commands which generally
require a commandline flag to include submodules (git add,
git rebase).

Change-Id: Ide8a292162a42ab35145b5c4ca8ca0d020cdfe81
2019-05-02 18:21:42 -07:00
266f74c888 info: Use the non-formatting printer for headtext
If "repo init" was run in a path containing "%", "repo info" would fail
printing the path with

    File ".repo/repo/color.py", line 173, in f
      return fmt % args
    TypeError: not enough arguments for format string

as the "%" in the path name is interpreted as the start of a formatting
specifier. Avoid that by using the non-formatting printer for headtext
which does not require any formatting so there is no need to try to
expand "%" sequences.

Change-Id: Ie193b912191fe7cdabdce5c97bb100f0714f6e76
Signed-off-by: Sebastian Schuberth <sschuberth@gmail.com>
2019-04-17 19:46:05 +02:00
1f1596b473 Don't print "persistent ref" message when syncing quietly
The newly introduced "Already have persistent ref" message prevents
repo from overwriting the last line when syncing quietly. Omit the
message when syncing quietly to clean up the output and to restore
the previous behaviour.

Change-Id: Idf42751c67f95924d6de50092ba54d4c6fe93096
2019-04-15 14:37:17 +02:00
0d9b16d1d8 sync: deleted unused repos in reversed order (children before parent)
Bug: chromium:950002
Test: repo sync chromeos using release manifest file
Change-Id: I613df6a1973eb36acd806a703e72f5172554bcc7
2019-04-06 00:49:47 +08:00
a84df06160 platform_utils_win32: remove an unnecessary workaround
The comment in _create_symlink is incorrect. The return value of
CreateSymbolicLink is as documented, it was just declared with
the wrong return type. The actual return type is BOOLEAN, not BOOL.

Fixing this allows us to simplify the code a bit.

Change-Id: I4d2190a50d45ba41dd9814bf7079a5784fc0a366
2019-03-21 23:45:59 +03:00
e57f1146de sync: respect --force-sync when fetching updates
If a tag is rewritten on the server (which is bad), trying to recover
locally with `repo sync --force-sync` doesn't actually work.  People
have to manually delete things themselves to fix syncing.  While tags
should never be rewritten in practice, allow users to easily recover
from broken servers.

We updated some of these code paths already (see commit 6e53844f1e
"Allow clobbering of existing tags from remote."), but the incremental
update flow was missed.

Bug: b/120778183
Bug: chromium:932651
Test: delete local tag & recreate to diff commit, then check
      `repo sync` & `repo sync --force-sync` behavior
Change-Id: I3648f7d2526732c06016b691a9a36c003157618d
2019-03-18 21:31:03 -04:00
01019d94af docs: fixed typo error.
Change-Id: Ic3ec1bfb150ec932e05ba5eda43537784f1fdcda
2019-03-18 13:38:33 +09:00
834d308a2b Merge "project: Relax the submodule name pattern to accept dots" 2019-03-14 07:14:28 +00:00
c18ee35da6 Merge "sync: Add option '--force-remove-dirty'" 2019-03-11 17:56:00 +00:00
d3c0f5914f sync: Add option '--force-remove-dirty'
Forcefully remove dirty projects if option '--force-remove-dirty' is given.
The '--force-remove-dirty' option can be used to remove previously used
projects with uncommitted changes. WARNING: This may cause data to be lost
since uncommitted changes may be removed with projects that no longer exist
in the manifest.

Change-Id: I844a6e943ded522fdc7b1b942c0a1269768054bc
2019-03-11 13:06:49 -04:00
41a26837d0 project: Relax the submodule name pattern to accept dots
Even if dots are used as separators for Git config keys, they are not
forbidden as part of submodule names. This fixes the issue of submodules
with a name like e.g. "long.js" to be skipped from checkout.

Change-Id: I77da07925ad207fa3d043067dfbbcb4a1ebdac4d
2019-03-11 13:53:38 +01:00
e7379dc5f7 docs: document a Python 3 migration plan
Bug: https://crbug.com/gerrit/10418
Change-Id: I72d82ce3a2d9af45d942bb10de82340110864ea5
2019-02-01 03:06:04 -05:00
13f323b2c2 event_log: turn id generation from a generator to a func call
Running lots of sync processes in parallel can hit the failure:
Fetching projects:  23% (124/523)Exception in thread Thread-201:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/local/src/repo/subcmds/sync.py", line 278, in _FetchProjectList
    success = self._FetchHelper(opt, project, *args, **kwargs)
  File "/usr/local/src/repo/subcmds/sync.py", line 357, in _FetchHelper
    start, finish, success)
  File "/usr/local/src/repo/event_log.py", line 104, in AddSync
    event = self.Add(project.relpath, task_name, start, finish, success)
  File "/usr/local/src/repo/event_log.py", line 74, in Add
    'id': (kind, next(self._next_id)),
ValueError: generator already executing

It looks like, while we lock the multiprocessing value correctly, the
generator that wraps the value isn't parallel safe.  Since we don't
have a way of doing that (as it's part of the language), turn it into
a plain function call instead.

Bug: https://crbug.com/gerrit/10293
Change-Id: I0db03601986ca0370a1699bab32adb03e7b2910a
2019-01-14 16:11:08 -05:00
12ee5446e9 init: Remove -c short option for --current-branch
This option conflicts with the gitc-init -c short option.

Bug: https://crbug.com/gerrit/10200
Change-Id: I06f37564429ca0bd4c0bbea6066daae4f663c838
2018-12-20 19:55:02 +00:00
e158e3802d Merge "README: link in new bug tracker" 2018-12-20 07:21:02 +00:00
3bbbcaf99d README: link in new bug tracker
Change-Id: I043afc0b77e709919e49ce548dff47776fddaddf
2018-12-20 02:11:46 -05:00
d4b13c280b Leverage the next keyword from python 2.7
This is literally what the next keyword is for.
https://www.python.org/dev/peps/pep-3114/

Change-Id: I843755910b847737b077ff2361ba3e04409db0f0
2018-12-19 11:06:35 -08:00
6e53844f1e Allow clobbering of existing tags from remote.
Bug: 120778183
Change-Id: Id44e2b68abc410a3afd4e07a3c943b0936347e38
2018-12-10 11:33:16 -08:00
d26146de7f platform_utils: Fix exception handling in _walk_windows_impl
Change-Id: I6b79cbc4c1bbbe17ffe8361fe1544434beaa9059
2018-11-06 09:25:35 +09:00
bd8f658823 Add option for git-repo to support 'silent' uploads
When --ne/--no-emails is added to 'repo upload' command line, gerrit
server will not generate notification emails.

project.py:Project.UploadForReview method is modified to accept a
string recognizable by gerrit to indicate different sets of destination
email addressees, but the upload command line allows only one option -
disable sending emails completely.

Default repo upload behavior is not being changed.

TEST=tried in the Chrome OS repo, observed that patches uploaded with
     --ne or --no-emails indeed do not trigger any emails, while
     patches uploaded without these command line options still trigger
     email notifications.

Change-Id: I0301edec984907aedac277d883bd0e6d3099aedc
2018-11-05 14:01:05 -08:00
713c5872fb upload: Unify option passing in ssh and other transports
Pass options through the refspec for all transports, including ssh.
This means the behavior will be more consistent between the ssh and
https cases.

A downside is that this prevents passing special characters in
reviewer options.  That already didn't work over https, so it seems
okay.  It could be fixed by using push options instead.

Change-Id: Ia38d16e350cb8cb0de14463bfb3d9724e13bc4bf
2018-11-05 13:32:22 -08:00
36391bf5ca Merge "init: --dissociate option to copy objects borrowed with --reference" 2018-10-28 23:30:50 +00:00
bed8b62345 Add support for long paths
* Add more file i/o wrappers in platform_utils to allow using
  long paths (length > MAX_PATH) on Windows.

* Paths using the long path syntax ("\\?\" prefix) should never
  escape the platform_utils API surface area, so that this
  specific syntax is not visible to the rest of the repo code base.

* Forward many calls from os.xxx to platform_utils.xxx in various place
  to ensure long paths support, specifically when repo decides to delete
  obsolete directories.

* There are more places that need to be converted to support long paths,
  this commit is an initial effort to unblock a few common use cases.

* Also, fix remove function to handle directory symlinks

Change-Id: If82ccc408e516e96ff7260be25f8fd2fe3f9571a
2018-10-22 08:16:35 -07:00
09f0abb0ef init: --dissociate option to copy objects borrowed with --reference
"repo init --reference" has two purposes: to decrease bandwidth used
at clone time, and to decrease disk usage afterward, by using the
reference repositories as an alternate store of objects even after
the clone. The downside is that it makes the borrowing repositories
dependent on the reference repositories, so it is easy to end up
with missing objects by mistake after a cleanup operation like "git
gc".

To prevent that, v2.3.0-rc0~30^2 (clone: --dissociate option to mark
that reference is only temporary, 2014-10-14), "git clone" gained a
--dissociate option that makes --reference reuse objects from the
reference repository at clone time but copy them over instead of
using the reference as an alternate. This is more straightforward to
use than plain --reference, at the cost of higher disk usage.

Introduce a --dissociate to "repo init" that brings the same benefits
to repo. The option is simply passed on to "git clone".

Change-Id: Ib50a549eb71e0a2b3e234aea57537923962a80d4
2018-10-19 23:51:23 +05:00
b3133a3164 Merge "update markdown/help header format" 2018-10-10 05:54:59 +00:00
3b24e7b557 update homepage URIs
Change-Id: I482a72bf296978625b1e82ef580b0e0d4d57ff25
2018-10-10 01:35:58 -04:00
b8f7bb04d0 update markdown/help header format
Since gitiles recommends using # headers over ---/=== underlines,
change the manifest-format.md over and all our help texts.

Change-Id: I96391d41fba769e9f26870d497cf7cf01c8d8ab3
2018-10-10 01:28:43 -04:00
3891b7519d manifest-format: convert to markdown
The gitiles system doesn't render .txt files, so convert this to .md
for better display online.

Change-Id: Ie12e46daf008dd8c97ae2ffd21fb68bd948fe625
2018-10-05 19:32:51 -04:00
2b42d288c0 Windows: Add support for creating symlinks as an unprivileged user
See https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/
for announcement of new flag.

This change follow the same pattern as what was done in "go":
https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0

Change-Id: If1e99fefdd3f787598e695731019c34b9bfcd1c2
2018-10-03 09:41:09 -07:00
e469a0c741 fix some sync error while using python3
Change-Id: I70925e48756c356d48359679d8ad1b9e33a68595
2018-07-24 22:20:08 +08:00
65b0ba5aa0 Remove unused pylint suppressions
pylint is not used since bb5b1a0. The pyflakes cleanup mentioned in that
commit has not been done, but given that this project is no longer being
actively developed I don't think it's worth spending time doing it.

Leaving the pylint suppressions causes confusion because it leads people
to think that we are still using pylint.

Change-Id: If7d9f280a0f408c780f15915ffdb80579ae21f69
2018-07-24 22:20:08 +08:00
a6515fb952 Merge "Flush stderr on Windows" 2018-07-13 15:48:36 +00:00
993dcacd17 Fix the initial existence check for "repo"
Commit 27226e742d introduced a warning if
"repo" is not part of the bootstrapped REPO_URL. However, that check was
done too early, directly after the call to _Clone. As the _Clone function
does not actually clone but it only initializes and fetches, the check
needs to be moved to after the call to _Checkout.

To reproduce, call

repo init --no-clone-bundle --repo-branch=master -u https://android.googlesource.com/platform/manifest

which will currently always show the (bogus) warning message. With this
fix, the warning will only be shown if "repo" indeed does not exist.

While at it, also slightly improve the code by using os.path.join().

Change-Id: Ied89e24231addabab6075005065748df1ffa74c4
2018-07-13 17:21:47 +02:00
a9399846fa Flush stderr on Windows
While on Linux stderr is unbuffered, it is buffered on Windows. Always
flush stderr on Windows to ensure any error messages appear in the right
order to ease diagnosing.

Change-Id: I37300e384ecd3a51a321a48818f0114d6f3357a0
2018-07-13 16:23:50 +02:00
b10f0e5b9a hooks/pre-auto-gc-battery: allow gc to run on non-laptops
Desktops and servers tend to have no power sensor, thus on_ac_power returns
255 ("unknown").  Thus, let's take any answer other than 1 ("battery") as
no contraindication to run gc.

If that tool returns "unknown", there's no point in querying other sources
as it already queried them, and is smarter than us (can handle multiple
adapters).

Reported by: Xin Li <delphij@google.com>
Signed-off-by: Adam Borowski <kilobyte@angband.pl>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
(cherry picked from git.git commit 781262c5e7ad4a7813c528803117ed0d2e8c5172)
Signed-off-by: Fredrik Roubert <roubert@google.com>
Signed-off-by: Jonathan Nieder <jrn@google.com>
Change-Id: I51fe2eb1eb879492a61e8e09c86ee34d049036c1
2018-07-11 13:45:58 -07:00
da40341a3e manifest: Support a default upstream value
It's convenient to set upstream for all projects in a manifest instead of
repeating the same value for each project.

Change-Id: I946b1de4efb01b351c332dfad108fa7d4f443cba
2018-05-09 14:58:18 -06:00
8d4b106642 Merge "docs: repo-hooks: fix cwd details" 2018-04-26 07:57:46 +00:00
ed429c9f6f docs: repo-hooks: fix cwd details
The hooks are run from the top of the manifest checkout, not from the
individual git repos.  It's up to individual hooks to chdir as needed.

Change-Id: I53325e0c3dcaa9c250b02b223e78d238d2cbd36d
2018-04-25 00:14:06 -04:00
0f2e45a3a6 Pass refs to ls-remote
This will fix the issue of parsing large output locally

Change-Id: I9a5cf1238147a02c92a3fca53eab9bd57f9d16b4
2018-03-24 13:00:08 +05:30
cf7c0834cf Download latest patch when no patch is specified
When someone does "repo download -c <project> <change>"
without specifying a patch number, by default patch 1 is
downloaded. An alternative is to look for the latest patch
and download the same when no explicit patch is given.
This commit does the same by identifying the latest patch
using "git ls-remote".

Change-Id: Ia5fa7364415f53a3d9436df4643e38f3c90ded58
2018-03-17 16:29:23 +05:30
4ea1f0cabd Merge changes I9c1ab65f,I7b2027ae
* changes:
  init: Remove string concat in no-op os.path.join
  Support relative paths in --reference
2018-03-16 02:06:10 +00:00
7d52585ec4 Add a way to override the revision of an <extend-project>
This change adds support for the 'revision' attribute in
<extend-project>. This avoids the need to perform a <remove-project>
followed by a <project> in local manifests.

Change-Id: Id2834fcfc1ae0d74b3347bed3618f250bf696b1f
2018-03-15 09:55:54 -07:00
1f365701b3 Merge "implement optional 'sync-tags' in the manifest file" 2018-02-26 06:50:53 +00:00
ce7e02601c Take care of a tilde on cookie file path
This handles cookie file path like "~/.gitcookies".

Change-Id: I87ba120a940fff38073d520f83b70654e6a239ba
2018-02-26 08:53:08 +09:00
a32c92c206 implement optional 'sync-tags' in the manifest file
Allow the 'default' and 'project' element in the manifest
file to apply "--no-tags" option equivalent.

Change-Id: I7e0f8c17a0e25cca744d45df049076d203c52ff5
Signed-off-by: YOUNG HO CHA <ganadist@gmail.com>
2018-02-14 16:57:41 +09:00
5f0e57d2ca init: Remove string concat in no-op os.path.join
This also fixes a line length warning.

Change-Id: I9c1ab65f83a35581dd657a707c7bc3c69db2b1dc
2018-01-22 11:00:24 -06:00
baa0009355 Support relative paths in --reference
Put the correctly-expanded relative paths in objects/info/alternates.
From gitrepository-layout(5), this path should be "relative to the
object database, not to the repository".

Change-Id: I7b2027ae23cf7d367b80f5a187603c4cbacdb2de
2018-01-22 10:57:29 -06:00
685320b000 event_log: Fix order of parameters to Add method call
Change-Id: I5add20eadfde39806ef4b2cc819da0ae0bfec2f5
2018-01-10 11:05:14 +09:00
02c0ee6ae6 Sync correctly when subproject url is a relative url to its parent url
Issue: when subproject url is a relative in .gitmodules
repo tool cannot handle this and cause:
"fatal: '***' does not appear to be a git repository
 fatal: Could not read from remote repository."
issue.

Signed-off-by: Shouheng Zhang <shouheng.zhang@intel.com>
Change-Id: I2a24c291ea0074ba13a740b32a11c0c25975e72b
2017-12-21 09:20:25 +08:00
1dc36600ef Merge "Update commit-msg hook to version from Gerrit 2.14.6" 2017-12-07 01:13:17 +00:00
cbe8aeb52b Update commit-msg hook to version from Gerrit 2.14.6
Change-Id: I14403fea4d017b97be5131e695803f121d404af2
2017-12-06 10:42:46 -08:00
305a2d029f Support --push-option in upload subcommand
Change-Id: I44836f8c66ded5a96cbf5431912e027e681f6529
2017-11-13 15:48:49 -08:00
84e7e16d35 document repo hooks mechanism
Change-Id: I9e25b92c846f887f515efcc706cf5a869645e0ec
2017-11-10 21:53:59 -05:00
f46902a800 forall: Clarify expansion of REPO_ environment values with -c
If a user executes:

  repo forall -c echo $REPO_PROJECT

then $REPO_NAME is expanded by the user's shell first, and passed
as $1 to the shell that executes echo. This will either result in
no output, or output of whatever REPO_NAME is set to in the user's
shell. Either way, this is an unexpected result.

The correct way to do it is:

  repo forall -c 'echo $REPO_PROJECT'

such that $REPO_NAME is passed in to the shell literally, and then
expanded to the value set in the environment that was passed to
the shell.

Update the documentation to make this clearer.

Change-Id: I713caee914172ad8d8f0fafacd27026502436f0d
2017-10-31 13:07:55 +09:00
c00d28b767 Set GIT_SSH_VARIANT when setting GIT_SSH
Make it explicit that the ssh wrapper we use for control master
support accepts OpenSSH-compatible command line arguments instead of
asking Git to guess.

The GIT_SSH_VARIANT setting was introduced in Git v2.13.0-rc0~3^2~2
(2017-02-01) as a more reliable detection method than relying on the
ssh command name.  Fortunately the default variant was 'ssh' (i.e.,
OpenSSH-compatible) so this wasn't initially required.

Now Git wants to start using more OpenSSH features
(-o SendEnv=GIT_PROTOCOL), and in order to do so its ssh variant
detection will need to be tweaked.  Set GIT_SSH_VARIANT explicitly
so this helper can continue to work regardless of how Git modifies
its autodetection.

Reported-by: William Yan <wyan@google.com>
Change-Id: I6bf2c53b4eb5303a429eae6cb68e0a5ccce89064
2017-10-19 14:39:26 -07:00
788e9626cc Provide more specific error message for symlinks errors on Windows
Change-Id: Ia6099beef37ae6b6143eba243fe7fbe02b74a9bb
2017-08-31 13:49:59 -07:00
cd892a38a6 Allow quotes in editor command on Windows
This change allows setting the EDITOR env. variable to point to a
program location that contains quotes and spaces.

For example:

> set EDITOR="C:\Program Files (x86)\Notepad++\notepad++.exe" -multiInst -nosession
> repo upload

Change-Id: Ic95b00f7443982b1956a2992d0220e50b1cf6bbb
2017-08-31 13:49:49 -07:00
010fed7711 Replace all os.remove calls
os.remove raises an exception when deleting read-only files on
Windows. Replace all calls with calls to platform_utils.remove,
which deals with deals with that issue.

Change-Id: I4dc9e0c9a36b4238880520c69f5075eca40f3e66
2017-08-31 13:49:36 -07:00
e8595e9df7 Support pager on Windows
Windows does not support pipe|fork, but we can simulate by creating
the pager as a child process, redirecting stdout/in/err appropriately
and then waiting for the child process to terminate after we are
done executing the repo command.

Change-Id: I5dd2bdeb4095e4d93bc678802e53c6d4eda0235b
2017-08-31 13:49:26 -07:00
227ad2ef42 Implement islink, readlink and realpath using Win32 api
Change-Id: I18452cbb32d24db73601ad10485dbe6bb278731c
2017-08-31 13:49:01 -07:00
2a4be94878 Handle Windows line endings when reading binary files
Without this change, '.git\HEAD' files, for examples, are sometime
read incorrectly resulting in the current branch to be reset to
"master" when running a "repo init -b xxx" on an already initialized
repository.

Change-Id: I48c7ef85ff81626edf156914329a560e14252f2a
2017-08-31 12:13:52 -07:00
9d743397bf Merge "Fixed upload to remotes with the url ssh://hostname" 2017-08-30 15:11:22 +00:00
2c57d619bc Merge "Add option '--no-cert-checks' for 'upload' sub command." 2017-08-30 15:11:10 +00:00
d1ebc89a08 Merge changes from topic "windows-support"
* changes:
  Port os.rename calls to work on Windows
  Workaround shutil.rmtree limitation on Windows
  Add support for creating symbolic links on Windows
  Make "git command" and "forall" work on Windows
2017-08-30 10:24:03 +00:00
2ec2a5d64c Fixed upload to remotes with the url ssh://hostname
Change-Id: I1d0dd4d3f90eac45205f6f4ca98a29b0babdbc3f
2017-08-29 20:16:08 +00:00
9ead97bb51 When starting a branch, do not use a tag or change value for branch.merge
When starting a branch, branch.merge is set to project revision unless
the revision is a SHA1. In that case, branch.merge is set to dest_branch
if defined or manifest default revision otherwise. This special handling
allows repo upload to work when the project revision is a SHA1.

Extend the special handling to also happen when the project revision
is a tag value or a change value so that repo upload will work in those
case as well.

Change-Id: Iff81ece40e770cd02535e80dcb023564d42dcf47
2017-08-25 09:10:29 +09:00
e43322625a Print a message when fetching is skipped for an immutable ref
The output indicates that fetching happens even when it is skipped.

To avoid confusion, print a message when fetching is skipped for
an immutable ref so that the user knows when and why a fetch is skipped.

Change-Id: Id6e4812cebc5e57d379feb76a9d034af0b93043b
2017-08-25 00:00:02 +00:00
bed59cec5e Add option '--no-cert-checks' for 'upload' sub command.
This option allow to bypass verification ssl certification while
establishing connection with Gerrit to upload review.

Change-Id: If2e15f5a273c18a700eb5093ca8a4d5a4cbf80cd
2017-08-23 14:06:14 +02:00
c94d6eb902 Revert "Migrate git-repo to create private changes rather than drafts"
This reverts commit d88f53e2b9. I merged
it too hastily without paying enough attention to compatibility with
released Gerrit versions.

Change-Id: I4028d4737df1255f11e217da183a19a010597d5b
2017-08-08 18:34:53 +00:00
d88f53e2b9 Migrate git-repo to create private changes rather than drafts
Considering that some users might expect changes created with
'-d' option are not public. Private changes may be a better
choice here than work-in-progress changes.

Change-Id: I46a8fb9ae38beb41cf96d6abe82bea6db2439669
2017-08-07 15:08:18 +02:00
87984c6db4 Add options for git-repo to support private and wip changes
This change adds options for git-repo tool to support private
changes and work-in-progress changes.

Change-Id: I343491f5949f06f1580d53f9cc0dee2dca09130f
2017-08-07 15:02:39 +02:00
ffc1401327 Merge "download: try to choose . as default project if none" 2017-08-02 07:19:45 +00:00
8a6eeed7f5 Merge "Always print percentage when syncing quietly" 2017-08-02 07:01:08 +00:00
7be072efa6 Always print percentage when syncing quietly
Change-Id: I574396e63520781067ed1e991c41caf7640e5731
2017-07-15 16:44:55 +00:00
7482a96443 download: try to choose . as default project if none
Change-Id: I28b5e3be5f3c9a4c077af87d6a3e0cc3b96a1b9d
2017-07-12 10:15:06 +02:00
3bcd30545e Fix "list comprehension redefines 'x'" warnings from pyflakes
$ git ls-files | grep py$ | xargs pyflakes
  subcmds/stage.py:101: list comprehension redefines 'p' from line 63
  subcmds/sync.py:784: list comprehension redefines 'p' from line 664
  subcmds/upload.py:467: list comprehension redefines 'avail' from line 454

Change-Id: Ia65d1a72ed185ab3357e1a91ed4450c719e75a7c
2017-07-10 23:26:04 +00:00
224a31a765 init: add missing submodule arg
The submodule argument to Sync_LocalHalf was missing in
MetaBranchSwitch, causing submodules not to get synced when the
-b/--manifest-branch argument to init is used.

Change-Id: Ie86d271abac2020725770be36ead83be3326e64b
Signed-off-by: Martin Kelly <mkelly@xevo.com>
2017-07-10 14:50:52 -07:00
b54343d9fd Tell the user if it will upload a draft
Change-Id: Ie004ec9d61603f3f618c47597947b82c59f2839c
2017-07-10 13:20:01 +00:00
259f16520a Merge "Add a newline after "Fetching projects" progress output" 2017-06-28 04:24:03 +00:00
8419ab22d6 sync: Continue job if some fetchs failed but force-broken is set
With --force-broken it continue to fetch other projects but nothing
is added in directory because it abort some lines later.

Change-Id: I32c4a4619b3028893dc4f98e8d4e5bc5c09adb27
2017-06-16 12:23:26 +02:00
913327f10c Add a newline after "Fetching projects" progress output
Output before change:

    Fetching project platform/packages/providers/UserDictionaryProvider
    Fetching projects:  66% (773/1171)  Fetching project platform/external/regex-re2
    Fetching project device/generic/mini-emulator-x86_64

Output after change:

    Fetching project platform/packages/providers/UserDictionaryProvider
    Fetching projects:  66% (773/1171)
    Fetching project platform/external/regex-re2
    Fetching project device/generic/mini-emulator-x86_64

Change-Id: I4da84da58316c69294e4da2792f83885dc942701
2017-06-13 13:03:39 +02:00
ad1abcb556 Port os.rename calls to work on Windows
os.rename fails on Windows if the destination exists, so replace
os.rename to platform_utils.rename which handles the platform
differences.

Change-Id: I15a86f10f65eedee5b003b80f88a0c28a3e1aa48
2017-05-29 19:33:07 +09:00
a65adf74f9 Workaround shutil.rmtree limitation on Windows
By default, shutil.rmtree raises an exception when deleting readonly
files on Windows.

Replace all shutil.rmtree with platform_utils.rmtree, which adds an
error handler to make files read-write when they can't be deleted.

Change-Id: I9cfea9a7b3703fb16a82cf69331540c2c179ed53
2017-05-29 19:32:31 +09:00
d5cec5e752 Add support for creating symbolic links on Windows
Replace all calls to os.symlink with platform_utils.symlink.

The Windows implementation calls into the CreateSymbolicLinkW Win32
API, as os.symlink is not supported.

Separate the Win32 API definitions into a separate module
platform_utils_win32 for clarity.

Change-Id: I0714c598664c2df93383734e609d948692c17ec5
2017-05-29 19:30:34 +09:00
2e70291162 Make "git command" and "forall" work on Windows
Python on Windows does not support non blocking file operations.
To workaround this issue, we instead use Threads and a Queue to
simulate non-blocking calls. This is happens only when running
with the native Windows version of Python, meaning Linux and Cygwin
are not affected by this change.

Change-Id: I4ce23827b096c5138f67a85c721f58a12279bb6f
2017-05-29 19:29:30 +09:00
35d22217a5 Ensure repo waits for child process to terminate
See http://stackoverflow.com/questions/7004687/os-exec-on-windows:

execv on Windows does not behave as on Linux, i.e. a new process is
spawned and the parent process terminates right away, which makes the
shell prompt come back too soon.

Change-Id: I1f8d23208765988629f081e9b949c67cf71c08ae
2017-05-29 13:56:18 +09:00
a24671f661 Merge "init: allow relative path on --reference argument" 2017-05-29 04:54:12 +00:00
e0684addee sync: Add support to dump a JSON event log of all sync events.
Change-Id: Id4852968ac1b2bf0093007cf2e5ca951ddab8b3b
2017-05-29 13:39:54 +09:00
fef9f21b28 Fix misplaced file separator string.replace call
Project names are stored as path using the '/' file separator, and
stored in a dictionary as keys.

Change-Id: Ide40dfe840958ac0d46caae5f77f1a49d71c9d90
2017-05-28 21:18:13 +09:00
6a470be220 Use OS file separator
Change-Id: I46b15bc1c1b4f2300a6fd98fe16c755da4910e7a
2017-05-28 21:16:15 +09:00
169d8ae93c Merge "pre-auto-gc: Add support for Windows" 2017-05-28 00:37:59 +00:00
c79d3b8fd1 init: allow relative path on --reference argument
Change-Id: I41d6be6bc035fdddb5a27c072994439986d58d58
Signed-off-by: YOUNG HO CHA <ganadist@gmail.com>
2017-05-28 00:33:25 +00:00
aa90021fbc Set result if sys.exit() is called by subcommand.
Allows the finally branch to make sure of the return code.

Change-Id: I7a796da5b60269cbd71aad953f1b9bb762b8eef8
2017-05-27 13:32:00 +09:00
fddfa6fbac Adding include element into the top-level element
The documentation of the XML file format contains DTD which contains
definition of all allowed elements and attributes. The "include" element
is defined but it's not referenced from the top-level "manifest"
element.

This patch is adding the "include" element into the list of elements of
the top-level "manifest" element.

Change-Id: I33beb8ef2846bbf42ffd42e6ae6888828566d604
2017-05-27 04:26:15 +00:00
997a92bd58 Merge "Add option REPO_IGNORE_SSH_INFO to ignore ssh_info" 2017-05-27 04:25:39 +00:00
fbcbcabe98 Merge "init: add --submodules to sync manifest submodules" 2017-05-27 04:24:58 +00:00
eec726c6d8 Add option REPO_IGNORE_SSH_INFO to ignore ssh_info
This is required for setups, where Gerrit access using ssh is only available
for some networks.
For network without ssh access, repo will get ssh_info from Gerrit and
use ssh for communications - which will fail. To support this setup
we need to have an option to ignore the ssh_info provided by Gerrit and
use http(s).

Using git insteadOf as alternative results in the inability to add
reviewers using "repo upload --re=...", since the syntax of adding
reviewers differs for ssh and https. repo is assuming an ssh
connection and uses "git push --receive-pack=...", which will fail
since git silently uses https for push operation. repo must be aware
that https is used so it uses "git push remote ...:refs/for/...%r=..."
for upload.

Change-Id: Idd83baef0fb26ffcc9ac65e204b68d323ce177a1
2017-05-26 15:11:11 +02:00
666debc518 gitc_delete: Remove unused imports
Change-Id: I672189ba99e18dca3956e2396c921d1ef0ca2ddd
2017-05-26 21:53:34 +09:00
c354a9b922 abandon: fix usage of undefined variable
As reported by pyflakes:

  subcmds/abandon.py:84: undefined name 'p'

The name of the variable should be 'proj'.

Change-Id: Ic09eb92e8db6b510e99efce010bd0bb094d7cbfe
2017-05-26 21:52:23 +09:00
06848b2415 Update .mailmap
Change-Id: Icabc7fb2161b661c2df9290a1ca6f75b9b1b8e1b
2017-05-26 21:44:57 +09:00
e4e94d26ae init: add --submodules to sync manifest submodules
repo sync can sync submodules via the --fetch-submodules option.
However, if the manifest repo has submodules, those will not be synced.
Having submodules in the manifest repo -- while not commonly done -- can
be useful for inheriting a manifest from another project using <include>
and layering changes on top of it.  In this way, you can avoid having to
deal with merge conflicts between your own manifests and the other
project's manifests (for example, if you're managing an Android fork).

Add a --submodule option to init that automatically syncs the submodules
in the manifest repo whenever the manifest repo changes.

Change-Id: I45d34f04517774c1462d7f233f482d1d81a332a8
Signed-off-by: Martin Kelly <mkelly@xevo.com>
2017-05-23 16:51:31 -07:00
c9439facdd pre-auto-gc: Add support for Windows
Previously, this would always have exited with 1 on Windows, causing "git
gc --auto" to abort. Fix this by adding support for Windows.

Change-Id: Ie519b366a11b6b18b2d465e892e738de3f4bbc99
2017-05-03 11:26:21 +00:00
3d7bbc9edf project.py: fix performance issue with --reference when the mirrored repository has many refs
Change-Id: Id0183903597f872eee80ca32a8050125b187a3d4
2017-04-12 20:04:47 +08:00
ffb4b89099 sync.py: report the remote URL on fatal git remote errors
repo can be configured to download from any number of remote git repos.
However when one fails repo doesn't report which one. Example:
Fatal: remote error: Daily ls-remote rate limit exceeded for IP xx.xx.xx.xx

TEST=repo init -q -u https://chromium.googlesource.com/chromiumos/manifest.git
  # Apply patch in ./.repo/repo/
  # Simulate a git remote error:
  sed -i -e 's#chromiumos/docs#chromiumos/XXdocs#' .repo/manifests/full.xml
  repo sync --quiet --force-sync docs
  # error message now shows the remote URL

Optional test tip: reduce the time.sleep(random(...)) in ./.repo/repo/project.py

Change-Id: I4509383b6a43a8e66064778e8ed612d8a735c8b6
2017-04-04 22:10:34 -07:00
04071c1c72 manifest-format: fix EMPTY keyword usage
The keyword EMPTY doesn't use parens.

BUG=git-repo:140

Change-Id: I7cd28a09c401520a72e5c244a77d9d70385f1b61
2016-12-28 16:07:16 -05:00
7de8c5db78 Merge "init: Add no-tags and current branch options" 2016-12-09 02:33:42 +00:00
bb9c42cf1d Merge "Fix removing broken symlink in reference dir" 2016-12-06 07:51:01 +00:00
f4dda9a1be init: Add no-tags and current branch options
This avoids fetching tags and branches for huge manifests

Change-Id: I19c9724d75364440b881b297d42b906f541f73ff
2016-12-01 19:03:41 -05:00
b881d227f3 Merge "Add a check and more output to protect against invalid REPO_URLs" 2016-10-29 07:28:35 +00:00
8e2d1d521e Merge "Fix checkout error when depth passed to repo init and revision is a sha1" 2016-10-28 19:30:45 +00:00
27226e742d Add a check and more output to protect against invalid REPO_URLs
If you don't know that the url to git-repo itself can be overridden via
REPO_URL, it's hard to debug cases where REPO_URL is accidentally set to
another repository, e.g. inside a Jenkins CI job. What makes is even
harder is that the ".repo/repo" directory gets silently removed in such
cases as verifications fails, which makes it impossible to look at the
cloned files to understand the problem.

To better protect against such an issue, warn if the cloned git-repo
repository does not contain a top-level "repo" file, and state that the
".repo/repo" directory will be removed in case of a clone failure.

Change-Id: I697b4999205a5967910c0237772ccaada01e74d4
2016-10-28 14:43:02 +02:00
6c5944606a Fix checkout error when depth passed to repo init and revision is a sha1
Currently, if direct fetch of a sha1 is not supported by git server and
depth option is used, we fallback on syncing the upstream branch by
ignoring depth option.

This fallback doesn't work in next 2 cases:
(1) upstream attribute is not specified in manifest
(2) depth option is passed to repo init command
    (not with clone-depth attribute in manifest)

This commit do the following:
- fixes (1) by updating condition used to apply fallback
  first we retry with depth set to None, then by syncing all branches
- fixes (2) by passing depth as argument of _RemoteFetch() method
  thus, its value is not set again to depth value passed to repo init
  command when applying fallback

Change-Id: Ifd6fffafc49ba229df624b0d7b64c83d47619d17
2016-10-28 14:29:57 +02:00
ae81c964b6 Merge "implement optional '--all' in the abandon command" 2016-10-28 07:52:31 +00:00
e02c17c9ea Merge "_CheckDirReference: log actual error before suggesting --force-sync" 2016-10-28 06:49:07 +00:00
6e31079033 Add sso to list of known schemes for relative URLs
repo already special-cases sso:// URLs to behave similarly to https://
and rpc:// elsewhere in repo, but it forgot to do so here.

Noticed when trying to use relative URLs in a manifest obtained using
an sso:// URL.

Change-Id: Ia11469a09bbd6e444dbc4f22c82f9bbe9f5fd083
2016-10-27 15:56:38 -07:00
ec287902e6 _CheckDirReference: log actual error before suggesting --force-sync
A recent backward incompatible change created confusion and loss of
productivity and highlighted the very limited amount of information
provided when repo sync fails; merely recommending to --force-sync
and blow-up git repos without any hint as to why. The addition of
this basic _error(...) call would have provided a clue and will in
the future.

BUG=Issue 232
TEST=simulate a breakage similar to the ones reported at
  https://groups.google.com/a/chromium.org/forum/#!topic/chromium-os-dev/2-0oCy_CX5s
  cd .repo/projects/src/third_party/libapps.git/
  file info; rm info; ln -s wronglink info
  cd -
  repo sync src/third_party/libapps/
  # error message now shows the failure

Change-Id: Idd2f177a096f1ad686caa8c67cb361d594ccaa57
2016-10-27 12:58:26 -07:00
4d5bb68d58 status: add -q/--quiet option
The --quiet option reduces the output to just
a list of projects with modified workspaces (and
orphans if -o is specified)

A common use case is when performing a full-workspace
merge.  The integrator will kick-off a merge via:

    repo forall -c git merge <some tag>

And then produce a short list of conflicted projects via:

    repo status -q

The integrator can then iteratively fix and clean up all conficted
components.  The merge is complete when:

    repo status -q

    returns no output.

Change-Id: Ibbba8713eac35befd8287c95948874e23fd5c7e2
2016-10-17 15:24:09 -05:00
2e14792a94 implement optional '--all' in the abandon command
when you want to delete all local branches, you should be find
all branches' name, and type them behind 'repo abandon' command.

Usage:
    repo abandon --all [<project>...]

Change-Id: I4d391f37fb9d89b8095488c585468eafc1a35f31
2016-10-17 02:29:42 +00:00
82f67987a3 Merge "sync: Fix semaphore release bug that causes thread 'leaks'" 2016-10-17 01:10:16 +00:00
699bcd40be Removed duplication code in abandon.py
code about getting argument is duplicated.
so this line is removed

Change-Id: Id321b999c7dacdb403cd986cbf35f8db62efc157
2016-10-12 10:02:30 +09:00
7f1ccfbb7b sync: Fix semaphore release bug that causes thread 'leaks'
When repo syncs a manifest that utilizes multiple branches
in the same project, then the sync will use an extra
thread for each "duplicate".  For example, if
the manifest includes the project "foo" and "bar"
twice, then "repo sync -jN" will fetch with N+2 threads.

This is caused by _FetchHelper() releasing the thread semaphore
object each time it's called, even though _FetchProjectList()
may call this function multiple times within the scope of a
single thread.

Fix by moving the thread semaphore release to
_FetchProjectList(), which is only called once per thread
instance.

Change-Id: I1da78b145e09524d40457db5ca5c37d315432bd8
2016-10-11 14:10:34 -05:00
eceeb1b1f5 Support broken symlinks when cleaning obsolete paths
When there's a symlink to a directory, os.walk still lists the symlink
in dirs, even if it isn't configured to follow symlinks. This will fail
the listdirs check if the symlink is broken (either before or during the
cleanup). So instead, check for directory symlinks and remove them using
os.remove.

Bug: Issue 231
Change-Id: I0ec45a26be566613a4a39bf694a3d9c6328481c2
2016-09-27 03:05:11 +00:00
16889ba43d Revert "Repo: fall back to http, if ssh connection fails for http repos"
This reverts commit 488bf092d5.

Issue 230

Change-Id: I3a5725301f576e1a2ac499cb6daa631895115640
2016-09-22 16:40:27 +00:00
40d3952270 Merge "On project cleanup, don't remove nested projects" 2016-09-21 22:23:44 +00:00
4350791e0d On project cleanup, don't remove nested projects
When there are nested projects in a manifest, like on AOSP right now:

<project path="build" name="platform/build" />
<project path="build/blueprint" name="platform/build/blueprint" />
<project path="build/kati" name="platform/build/kati" />
<project path="build/soong" name="platform/build/soong" />

And the top "build" project is removed (or renamed to remove the
nesting), repo just wipes away everything under build/ and re-creates
the projects that are still there. But it only checks to see if the
build/ project is dirty, so if there are dirty files in a nested
project, they'll just be blown away, and a fresh worktree checked out.

Instead, behave similarly to how `git clean -dxf` behaves and preserve
any subdirectories that have git repositories in them. This isn't as
strict as git -- it does not check to see if the '.git' entry is a
readable gitdir, just whether an entry named '.git' exists.

If it encounters any errors removing files, we'll print them all out to
stderr and tell the user that we were unable to clean up the obsolete
project, that they should clean it up manually, then sync again.

Change-Id: I2f6a7dd205a8e0b7590ca5369e9b0ba21d5a6f77
2016-09-20 17:16:12 -07:00
d648045366 implement optional 'pushurl' in the manifest file
Allow the 'remote' element in the manifest file to define an optional
'pushurl' attribute which is passed into the .git/config file.

Change-Id: If342d299d371374aedc4440645798888869c9714
Signed-off-by: Steve Rae <steve.rae@raedomain.com>
2016-09-20 15:31:20 +00:00
628456833a Merge "Repo: improve error detection for new ssh connections" 2016-09-20 08:06:12 +00:00
2aa61d0bc8 Merge "Repo: fall back to http, if ssh connection fails for http repos" 2016-09-20 08:05:57 +00:00
4aed6f8c7d Merge "Replace pylint with pyflakes/flake8" 2016-09-20 06:57:16 +00:00
01b7d758d5 Merge "When syncing a project with a shared object store, disable automatic pruning." 2016-09-14 20:48:24 +00:00
267ac57361 Merge "repo: add comment for updating maintainer keys" 2016-09-14 14:06:42 +00:00
bb5b1a076b Replace pylint with pyflakes/flake8
pylint reports a lot of warnings, but many of them are false positive,
and it's difficult to configure it. It also seems that for some reason
the included config file is not working well with the latest version.

Update the documentation to recommend using pyflakes and flake8 instead
of pylint. Remove the pylint config and add a basic flake8 config with
minimum settings:

- Maximum line length 80 columns
- Ignore warnings about indentation (repo uses 2 rather than expected 4)
- Ignore warnings about import placement

In this commit no code cleanup is done, and it's expected that most of
the files will throw up quite a few warnings, at least for flake8. These
can be cleaned up in follow-up commits.

The existing pylint suppression comments are left as-is. These will be
helpful when cleaning up pyflakes warnings later.

Change-Id: I2f7cb4340266ed07cc973ca6483b8f09d66a765b
2016-09-14 09:49:02 +02:00
e01ee026e6 Merge "Fix submodule checkout error when using sync-s option" 2016-09-14 07:45:50 +00:00
e4433653db repo: add comment for updating maintainer keys
Change-Id: Ic1e7557f9597234033561ab9fb3104b87e30015e
2016-09-14 01:28:30 -04:00
d9de945d8a Merge "upload: short circuit when nothing is pending" 2016-09-14 05:20:36 +00:00
2ff302929c When syncing a project with a shared object store, disable automatic pruning.
The shared object stores confuse git and make it throw away objects which are
still in use. We'll avoid that problem by disabling automatic pruning on those
projects, but there's nothing preventing a user from changing the config back
or pruning a repository manually.

BUG=chromium:375945
TEST=Ran repo sync on fresh ChromeOS checkout, starting with a branch of repo
with this change. Verified that the kernel projects and no others were
identified as having shared object stores, and that repo successfully disabled
automatic pruning in their configs. Re-enabled pruning and ran repo sync just
on one of the kernel directories. Verified that pruning was re-disabled as a
result.

Change-Id: I728ed5b06f0087aeb5a23ba8f5410a7cd10af5b0
2016-09-14 00:19:44 -04:00
e5c0ea0a95 Consider local project to be default for 'repo start'
The requirement to explicitly specify the local project when starting
a new repo branch is somewhat counter intuitive.

This patch uses the current directory's git tree as the default
project.

Tested by running

  'repo start <name>'

observed that the result is the same as if running

  'repo start <name> .'

Change-Id: If106caa801b4cd5ba70dbe8354a227d59f100aa3
2016-09-14 00:17:45 -04:00
163a3be18b upload: short circuit when nothing is pending
When nothing is pending, most of this code is already short-circuited.
Hoist the single check up to make this more obvious/slightly faster.

Change-Id: Iec3a7e08eacd23a7c5f964900d5776bf5252c804
2016-09-14 00:16:37 -04:00
7a77c16d37 Update mailmap
Order the entries alphabetically and add a couple more.

Change-Id: I8d98e8a5a1dd6868b566a42428030d2040b8af7a
2016-09-02 11:12:28 +09:00
488bf092d5 Repo: fall back to http, if ssh connection fails for http repos
if a gerrit server has ssh and https access enabled, but user access
(for some users) is limited to https, 'repo upload' command will fail
for them.

Gerrit returns a ssh configuration (gerrit/ssh_info), that does not work
for users limited to https.

With this patch repo will test, if the returned ssh configuration from
gerrit/ssh_info is working. if not, it will fall back to https for upload.

Change-Id: If98f472e994f350bf71f35610cd649b163f1ab33
2016-08-30 07:35:03 +00:00
05dc46b0e3 Repo: improve error detection for new ssh connections
this check can only detect errors that happen within 1 sec after launching
ssh. But this is typically enough to catch configuration issues like
'connection refused' or 'authentication failed'.

Change-Id: I00b6f62d4c2889b1faa6c820e49a198554c92795
2016-08-30 07:34:33 +00:00
39252ba028 gitc: Lower concurrent ls-projects requests
Too many requests at the same time is causing 502 errors.

Change-Id: Ic8fbb2fbb7fb6014733fa5be018d2dc02472f704
2016-08-23 14:19:00 -07:00
71e4cea6de RepoHook: do not list options twice during hash based approval
Instead of

  Do you want to allow this script to run (yes/yes-never-ask-again/NO)? (yes/always/NO)?

ask

  Do you want to allow this script to run (yes/always/NO)?

Change-Id: I5f5a2d0e88086a8d85e54fb8623a62d74a20956a
Signed-off-by: Jonathan Nieder <jrn@google.com>
2016-08-18 17:06:26 -07:00
c4c2b066d1 Increment the wrapper version
There have been a number of changes in the repo wrapper since the last
increment that was done in fee390ee:

- 9711a98 init: Add --no-clone-bundle option
- 631d0ec Support non-ASCII GNUPGHOME environment variable
- 4088eb4 repo: Cleaned up pylint/pep8 violations
- 5553628 repo: Add check of REPO_URL env variable
- 745b4ad Fix gitc-init behavior
- d3ddcdb Ignore clone.bundle on HTTP 501, i.e. Not Implemented

Change-Id: I3f763ef0ec2df2d726dff429021b48ad474148f1
2016-08-17 13:58:17 +09:00
6a0a3648f1 Merge "init: Add --no-clone-bundle option" 2016-08-17 04:57:28 +00:00
6118faa118 Merge "init: Respect --quiet option when synching manifest repository" 2016-08-17 04:04:14 +00:00
183c52ab02 Merge "project: Set config option to skip lfs smudge filter" 2016-08-17 01:00:12 +00:00
58f85f9a30 Merge "RepoHook: allow users to approve hooks via manifests" 2016-08-16 18:05:23 +00:00
40252c20f7 RepoHook: allow users to approve hooks via manifests
The constant prompting when registered hooks change can be tedious and
has a large multiplication factor when the project is large (e.g. the
AOSP).  It gets worse as people want to write more checks, hooks, docs,
and tests (or fix bugs), but every CL that goes in will trigger a new
prompt to approve.

Let's tweak our trust model when it comes to hooks.  Since people start
off by calling `repo init` with a URL to a manifest, and that manifest
defines all the hooks, anchor trust in that.  This requires that we get
the manifest over a trusted link (e.g. https or ssh) so that it can't
be MITM-ed.  If the user chooses to use an untrusted link (e.g. git or
http), then we'll fallback to the existing hash based approval.

Bug: Issue 226
Change-Id: I77be9e4397383f264fcdaefb582e345ea4069a13
2016-08-16 13:02:52 -04:00
76a4a9df86 project: Set config option to skip lfs smudge filter
During sync, repo runs `git read-tree --reset -u -v HEAD` which causes
git-lfs's smudge filter to run. However this fails because git-lfs does
not work with bare repositories.

Add lfs.filter configuration to the project config as suggested in the
comments on the upstream git-lfs client issue [1]. This prevents the
smudge filter from running, and the sync completes successfully.

For any projects that have LFS objects, `git lfs pull` must be executed.

[1] https://github.com/github/git-lfs/issues/1422

Bug: Issue 224
Change-Id: I091ff37998131e2e6bbc59aa37ee352fe12d7fcd
2016-08-16 21:55:36 +09:00
befaec1e56 improve docs
Change-Id: Ide4008f09c2f17f8fb3d85dfffe94544abfdd6a6
2016-08-16 00:14:28 -04:00
9711a98d6c init: Add --no-clone-bundle option
Bug: Issue 218
Change-Id: I42ba1f5fb9168875da0df6bdf4fe44c8d6498d54
2016-08-15 09:51:48 +09:00
438eade413 init: Respect --quiet option when synching manifest repository
Change-Id: Ib58b7dd971670e0888e6428333050700e776b0de
2016-08-15 09:51:48 +09:00
69297c1b77 Merge "Support non-ASCII GNUPGHOME environment variable" 2016-08-15 00:51:32 +00:00
8016f60a46 Merge "repo: Repo does not always handle '.' parameter correctly" 2016-08-14 08:50:28 +00:00
631d0ec708 Support non-ASCII GNUPGHOME environment variable
Here we don't need to encode this gpg_dir string when using
Python 2.7 on Linux.

Change-Id: I56724e9511d3b1aea61535e654a45c212130630d
2016-07-16 22:10:06 +03:00
f97e72e5dd Bail out when manifest is referencing a bad SHA-1 revision.
BUG: Issue 222
Change-Id: Ie0a64b39922d6fdf1be2989eb514985be8490278
2016-06-29 11:01:43 -07:00
8ac0c96537 Fix removing broken symlink in reference dir
Re-ordered to first create the symlink before checking the source
file and remove the destination if the source does not exists.

Change-Id: Iae923ba2ef0ba5a8dc1b8e42d8cc3f3708f773af
2016-06-29 05:43:01 +00:00
faaddc9b4e Merge "Adds additional crlf clobber avoidance." 2016-06-29 05:42:20 +00:00
a36af0767b Merge "Fix variable assignment" 2016-06-29 01:43:41 +00:00
037040f73e Fix variable assignment
If upstream string is empty, current_branch_only variable will be assigned
to an empty string.

This is not what we expect here as this variable is a boolean.

Change-Id: Ibba935e25a74c2be1e50c88b4b403cf394ba365e
2016-06-28 12:31:25 +02:00
2598ed06f1 Fix submodule checkout error when using sync-s option
When sync-s="true" option is used, the checkout of a submodule will try
to use the revision attribute of the parent project.

If this revision is a named reference, the checkout will fail if there
is no reference with this name in the submodule.

The proposed solution is to use the git commit id as revisionExpr for
submodules.

Change-Id: Ie8390a11957fd6a9c61289c6861d13cb3fa11678
2016-06-27 20:16:45 +02:00
01952e6634 Adds additional crlf clobber avoidance.
Adds the hook-scripts to .gitattributes due to the shell-scripts not
liking CRLF which they will get if a user sets 'autocrlf = true'
in their global gitconfig.

Further, since the python interpreter can handle either CRLF or LF, 
python-scripts specific line-ending rules have been removed.

Change-Id: I2d6bfd491b2f626b9ca93c40a3a7f2cfba6c54f0
2016-06-22 08:36:45 +00:00
9d2b14d2ec pylint: Fix unused-{argument,variable} warning
This commit fixes 4 out of the remaining 5 pylint warnings:

$ pylint --rcfile=.pylintrc *.py
************* Module gitc_utils
W:146, 0: TODO(sbasi/jorg): Come up with a solution to remove the sleep below. (fixme)
W:130, 6: Unused variable 'name' (unused-variable)
************* Module main
W:382,32: Unused argument 'fp' (unused-argument)
W:382,36: Unused argument 'code' (unused-argument)
W:382,42: Unused argument 'msg' (unused-argument)

Change-Id: Ie3d77b9a65b7daefaa9aa4b80f4b682c1678fd58
Signed-off-by: Stefan Beller <sbeller@google.com>
2016-06-21 11:48:57 -07:00
6685106306 pylint: fix indentation in manifest_xml
This fixes pylint warning:

************* Module manifest_xml
W:975, 0: Bad indentation. Found 8 spaces, expected 6 (bad-indentation)

Change-Id: I967212f9439430351836ebdc27e442d7b77476e2
Signed-off-by: Stefan Beller <sbeller@google.com>
2016-06-17 16:45:48 -07:00
d64e8eee51 pylint: ignore bad-whitespace
This ignores whitespaces errors, which we have quite a few of in argument
lists, for example:

************* Module git_config
C:209, 0: No space allowed around keyword argument assignment
  def HasSection(self, section, subsection = ''):
                                           ^ (bad-whitespace)
C:320, 0: No space allowed around keyword argument assignment
                   capture_stdout = True,
                                  ^ (bad-whitespace)
C:321, 0: No space allowed around keyword argument assignment
                   capture_stderr = True)
                                  ^ (bad-whitespace)
C:427, 0: Exactly one space required after comma
                     '-o','ControlPath %s' % ssh_sock(),
                         ^ (bad-whitespace)
C:436, 0: Exactly one space required after comma
    check_command = command_base + ['-O','check']
                                        ^ (bad-whitespace)
C:464, 0: Exactly one space required after comma
             % (host,port, str(e)), file=sys.stderr)
                    ^ (bad-whitespace)
C:707, 0: No space allowed around keyword argument assignment
    return self._config.GetString(key, all_keys = all_keys)
                                                ^ (bad-whitespace)
C:759, 0: No space allowed around keyword argument assignment
    return self._config.GetString(key, all_keys = all_keys)
                                                ^ (bad-whitespace)

Change-Id: Ia8f154f6741ce609787551f65877d7584c457903
Signed-off-by: Stefan Beller <sbeller@google.com>
2016-06-17 16:37:24 -07:00
8b39fb4bc0 Merge "Fix XmlManifest.Save with remotes that have 'alias' set" 2016-04-22 18:28:57 +00:00
96c2d65489 Fix XmlManifest.Save with remotes that have 'alias' set
When the alias attribute is set for a remote, the RemoteSpec attached to
a Project only contains the alias name used by git, not the original
name used in the manifest. But that's not enough information to
reconstruct the manifest, so save off the original manifest name as
another RemoteSpec parameter, only used to write the manifest out.

Bug: Issue 181
Bug: Issue 219
Change-Id: Id7417dfd6ce5572e4e5fe14f22924fdf088ca4f3
2016-04-22 10:32:06 +09:00
7ecccf6225 diffmanifests: support custom git pretty format strings
Change-Id: I29f4f1351c421f393328514d145df1a96aed9ee2
2016-04-21 18:36:11 +00:00
cee5c77166 Add a .mailmap file
Change-Id: I3c7e68fae0f8c082b2e0fbfc26cfb7dda31f1d34
2016-04-18 19:08:23 +09:00
79fba68e40 sync: Update help text for --smart-sync to be more specific
The --smart-sync option should return the manifest for *the latest*
known good build.

Change-Id: I2f3216b5b9e1af2ea5f9c3bf1c025813a3b77581
2016-04-13 18:03:00 +09:00
e868841782 Improve documentation of manifest server RPC methods
Mention that the RPC endpoints are used when running repo
sync with the --smart-sync and --smart-tag options

Change-Id: I4b0b82e8b714fe923a5b325a6135f0128bf636ff
2016-04-13 17:55:36 +09:00
f9fe3e14d2 repo: Repo does not always handle '.' parameter correctly
The repo script allows a manifest to specify a '.' as the path the
top-level directory, which co-locates the .git and .repo directories,
and places files from the git repository at the top-level:

  <project name="proj_name" path="." />
  <project name="sierra.other.git" path="other" />

Most commands work correctly with this setup. Some commands, however,
fail to find the project. For instance, 'repo sync' works, and 'repo sync .'
works in a sub-project ('other' in this case) but 'repo sync .' in the
top-level directory fails with the error:

error: project . not found

There are two reasons for this:

1. The self.worktree attribute of the Project object is not normalized,
so with a '.' for path its value would be '/my/project/root/.'. This is
fine when used as a path, since it's the same path as '/my/project/root',
but when used in a string comparison it fails. This commit applies
os.path.normpath() to that value before storing it.

2. The _GetProjectByPath method in command.py was not checking the path
against manifest.topdir, so even once it was normalized the project was
not found. This commit adds a check against manifest.topdir if the
loop drops out without finding a project.

Change-Id: Ic84d053f1bbb5a357cad566805d5a326ae8246d2
2016-04-08 00:07:52 +00:00
bdb866ea76 Fix symlinking of new projects
We weren't copying these lists, so the += was actually changing the
underlying lists.

When a new project was added to the manifest, we run _CheckDirReference
against the manifest project with share_refs=True, which added the
working_tree_* to the shareable_* lists. Then, when we load the new
manifest and create the new project, it uses the lists that already
contain the working_tree_* files, even though we passed
share_refs=False.

This happens reliably under the above conditions, but doesn't seem to
happen when syncing a fresh tree. So we've got a mixture of links that
may need to be cleaned up later. This patch will just stop it from
happening in the future.

Change-Id: Ib7935bfad78af1e494a75e55134ec829f13c2a41
2016-04-05 17:44:09 -07:00
e121ad558d Merge "Ignore clone.bundle on HTTP 501, i.e. Not Implemented" 2016-04-05 21:39:29 +00:00
1f0564406b Add --inverse-regex option to forall subcommand
Make it possible to exclude projects using regex/wildcard.

The syntax is similar to that of the -r option, e.g.:

  repo forall -i ^platform/ ^device/ -c 'echo $REPO_PROJECT'

Change-Id: Id250de5665152228c044c79337d3ac15b5696484
2016-04-05 07:28:27 +00:00
936d6185eb Merge changes from topic 'pylint-pep8-cleanup'
* changes:
  command.py: Cleaned up pylint/pep8 violations
  project.py: Cleaned up pylint/pep8 violations
2016-03-15 00:31:12 +00:00
9322964d14 Update commit-msg hook to version from Gerrit 2.12.1
Change-Id: I31b74aba998f8e83f370a759218777f2557a8872
2016-03-14 10:08:33 +09:00
4aa4b211c6 RepoHook: set __file__ when running the hook
A common design pattern is to use __file__ to find the location of the
active python module to assist in output or loading of related assets.
The current hook systems runs the pre-upload.py hook in a context w/out
that set leading to runtime errors:

$ repo upload --cbr .
ERROR: Traceback (most recent call last):
  File ".../repo/project.py", line 481, in _ExecuteHook
    self._script_fullpath, 'exec'), context)
  File ".../repohooks/pre-upload.py", line 32, in <module>
    path = os.path.dirname(os.path.realpath(__file__))
NameError: name '__file__' is not defined

Define this variable in this context so code can safely use it.

Change-Id: If6331312445fa61d9351b59f83abcc1c99ae6748
2016-03-05 21:52:31 +00:00
8ccfa74d12 command.py: Cleaned up pylint/pep8 violations
I noticed when running pylint (as the SUBMITTING_PATCHES file directs)
that there were a few violations reported. This makes it difficult
to see violations I might have introduced. This commit corrects all
pylint violations in the command.py script.

This script now has a pylint score of 10.0.

Change-Id: Ibb35fa9af0e0b9b40e02ae043682b3af23286748
2016-03-02 09:05:45 -07:00
30b0f4e022 project.py: Cleaned up pylint/pep8 violations
I noticed when running pylint (as the SUBMITTING_PATCHES file directs)
that there were a number of violations reported. This makes it difficult
to see violations I might have introduced. This commit corrects all
pylint violations in the project.py script.

This script now has a pylint score of 10.0, and no violations reported
by pep8.

Change-Id: I1462fd84f5b6b4c0dc893052671373e7ffd838f1
2016-03-02 09:05:40 -07:00
203153e7bb Add rpc: to default protocol whitelist
Change-Id: I57e1c3d93c0ce56da9c487df65eb3d258e0260e8
2016-02-26 18:53:54 -08:00
4cfb6d7167 Better error display on forall
It was only displaying 'Project list error: GitError()'
without any useful info about the project nor the error

Change-Id: Iad66cbaa03cad1053b5ae9ecc90d7772aa42ac13
2016-02-18 01:29:54 +00:00
b29e61133e SUBMITTING_PATCHES: Expand instructions
This commit adds additional instructions on getting patches submitted,
based on my recent experience doing so.

Change-Id: I8e0d37d316214cc9a39383414773aad181f83f18
2016-02-17 02:45:08 +00:00
4088eb434b repo: Cleaned up pylint/pep8 violations
I noticed when running pylint (as the SUBMITTING_PATCHES file directs)
that there were a number of violations reported. This makes it difficult
to see violations I might have introduced. This commit corrects all
pylint violations in the repo script.

First I ran this to clean up the formatting:

 autopep8 --max-line-length=80 --indent-size 2 repo

Following that the following violations remained:

% pylint --rcfile=.pylintrc repo
************* Module repo
W:220,21: Redefining name 'init_optparse' from outer scope (line 156)
(redefined-outer-name)
W:482, 2: No exception type(s) specified (bare-except)
C:704, 0: Old-style class defined. (old-style-class)

For line 220, the parameter to _GitcInitOptions was renamed so as not to
mask the init_optparse global.

For line 482, a pylint directive was added to disable the bare-execpt
violation for just that line.

For line 704, the _Options class was changed to subclass object.

Additionally, the comments at lines 107-113 were spaced out to line up
with the comment at line 112 that autopep8 moved.

This script now has a pylint score of 10.0

Change-Id: I779b66eb6b061a195d3c4372b99dec1b6d2a214f
2016-02-15 10:29:02 -07:00
5553628601 repo: Add check of REPO_URL env variable
We want to be able to run repo on a system that is not connected to
the Internet and cannot access https://gerrit.googlesource.com. We
can put a clone of that repos there, but would prefer to use the
stable version of the repo script instead of a locally modified
version.

This commit adds a check for the REPO_URL environment variable. If
that is set and not empty its value will be set in the REPO_URL
global in repo.  Otherwise the standard path will be used.

Change-Id: I0616f5f81ef75f3463b73623b892cb5eed6bb7ba
2016-02-09 17:27:29 -07:00
5ed805a98e Merge "Fix typos for manifest dtd" 2016-02-04 22:58:20 +00:00
985ac6b946 Merge "Fix prune when bare git has detached head" 2016-02-04 22:44:20 +00:00
ecf0a6c92b Merge "GITC: Fix 'repo start <branch> <repo>/<subdir>'" 2016-02-04 22:36:05 +00:00
04197a5144 GITC: Fix 'repo start <branch> <repo>/<subdir>'
As soon as we wrote the gitc manifest, the folder for that repo became
empty, causing the next GetProjects lookup to fail. Reorder the
GetProjects calls so that they all happen while we still have the
repository contents available.

If you were already in a subdir, for cases like 'repo start <branch> .',
this would still fail, since the working directory would disappear out
from under you. That's fine most of the time, since we shouldn't be
doing operations based on the local directory, but git has a realpath
function that tries to restore CWD by chdir'ing back to it. So if the
working directory no longer exists, chdir to the topdir before
continuing.

Change-Id: Ibdf6cd37ff6e5a5f8338347c3919175491f7166f
2016-02-04 14:31:55 -08:00
0b4cb325c6 Add option to rebase onto project's manifest version
Some teams have a continuous build server that would mark certain
manifest green and safe to sync to.  Then team members could repo
sync to that particular manifest file and make sure they always
sync to a green build.  But if she/he has some local changes and
wants to rebase, currently it would be a manual process to find the
correct version to rebase onto.  This patch helps with that use
case by automating the process to rebase onto the currently synced
manifest version.

Change-Id: I847c9eb6addf7f84fd3f5594fbf8c0bcc103f9a5
2016-01-28 10:20:03 -08:00
1a799d14b7 Fix prune when bare git has detached head
We don't really use HEAD much in the bare git repositories, but there
have been reports of errors in git-symbolic-ref:

  symbolic-ref: fatal: Refusing to point HEAD outside of refs/

That happen when the bare git repo is in the detached head state. It's
possible that previous operations were killed while we were pruning
branches.

Use DetachHead instead of SetHead if we're restoring the repo into a
detached head state.

Change-Id: I9062e8957bc70367d3ded399685ac026fbb421fc
2015-12-15 14:22:40 -08:00
827e547d9e Fix typos for manifest dtd
Change-Id: If53721544eca570e2bcce4598cdc2670a679c681
2015-12-11 00:05:54 +00:00
e9becc079c Sync: Fix error exit code when both -n and -f are used
When repo sync is used with -f (--force-error) and a project fails to
sync, the sync will continue but then exit with an error status.

However if -n (--network-only) is also used, the exit code is 0, even
when a project failed.

Modify the logic to make sure the sync exits with the correct status.

Bug: Issue 214
Change-Id: I0b5d97a34642c5aa3743750ef14a42c9d5743c1d
2015-11-26 02:25:43 +00:00
466b8c4ea2 Set GIT_ALLOW_PROTOCOL to limit dangerous protocols
See git commit 33cfccbbf35a -- some protocols allow arbitrary command
execution as part of the URL. Instead of blindly allowing those,
whitelist the allowed URL protocols unless the user has already done so.

Bug: Issue 210
Change-Id: I6bd8e721aa5e3dab53ef28cfdc8fde33eb74ef76
2015-11-26 11:03:19 +09:00
e1e0bd1f75 Check for broken links when updating linkfiles
If a linkfile is a broken link (destination does not exist), and it
needs to be updated, we didn't notice that it needed to be removed
first. Use lexists instead of exists to check for this condition.

Change-Id: I1f6a1f0193d3fd2b9f7a647836044997f6ab32eb
2015-11-18 16:51:51 -08:00
74cfd2709b Sync: Add option to prune refs during sync
By passing --prune to the sync command, the --prune option is
given to the `git fetch`, causing refs that no longer exist on
the remote to be removed.

Change-Id: I3cedacce14276d96ac2d5aabf2d07fd05e92bc02
2015-10-27 03:04:17 +00:00
c2a64ddffd A couple of fixes to the init command's -p option.
Adds windows as one of the allowed platforms flags.
Fixes -p foo to append 'platform-foo', instead of each letter (list.extend
expects a list and thus appends each char in the string, rather than the
string itself).

Change-Id: I73a92127ac29a32fc31b335cc54a246302904140
2015-10-22 13:28:20 -07:00
745b4ad660 Fix gitc-init behavior
With gitc-init, a gitc client may be specified using '-c'. If we're
not currently in that client, we need to change directories so that
we don't affect the local checkout, and to ensure that repo is
checked out in the new client.

This also makes '-c' optional if already in a gitc client, to match
the rest of the init options.

Change-Id: Ib514ad9fd101698060ae89bb035499800897e9bd
2015-10-07 15:43:22 -07:00
4c5f74e452 Sync: Add HTTP Cookie File header on temporary cookie file
The .gitcookies file generated by googlesource.com does not have
the header:

 # (Netscape) HTTP Cookie File

which causes python's MozillaCookieJar.load to fail with the
error:

 "does not look like a Netscape format cookies file"

Prepend the expected header onto the generated cookie file.

We don't bother to check if the header already exists on the
file; repeating it does not cause any problem.

Bug: Issue 207
Change-Id: I7d39720a1d36a6aae00f70691156514ebc04e579
2015-10-02 11:12:05 +09:00
b1ad2190a2 Sync: Don't fail when git cookies can't be loaded
If the git cookies file fails to load, use a default
cookie jar instead.

Bug: Issue 207
Change-Id: I7cb326c204f2784ab4dbd13801b3186667af5b78
2015-10-02 11:04:01 +09:00
f231db11a2 GITC: Add repo gitc-delete command.
repo gitc-delete deletes a GITC client and all the locally
saved sources. Useful for removing unnecessary clients and
recovering disk space.

Change-Id: Idf23addcea52b8713d268c34a7b37da0c5e5cd26
2015-10-01 21:05:17 +00:00
79360640f4 Add GitcClientCommand class for GITC-specific commands
These won't show up as common commands in the help text unless in a GITC
client, and will refuse to execute.

Change-Id: Iffe82adcc9d6ddde9cb4b204f83ff018042bdab0
2015-09-29 13:46:34 -07:00
7b01b2fd01 Merge "launcher: Update repo after applying clone.bundle" 2015-09-14 23:40:05 +00:00
aad84232ca Merge "docs: add copyfile and linkfile elements description" 2015-09-14 10:39:21 +00:00
3c03580607 fixed typo in gitc_init.py help output
Change-Id: I86459bf63297487457d6c4c995dfd1e63133ec53
2015-09-11 14:11:30 -04:00
54527e7e30 docs: add copyfile and linkfile elements description
The "copyfile" element is available since 2009 and
have been used in every Android manifest; the "linkfile"
element is available since 2014.
Now it's a good time to add both to the documentation

Change-Id: Ia987edf5f69a006235fbd3f33b744e9794a6d964
Signed-off-by: Ruslan Bilovol <ruslan.bilovol@gmail.com>
2015-09-10 09:43:19 +00:00
5ea32d1359 GITC: Always update the gitc manifest from the repo manifest
This way any changes made to the main manifest are reflected in the gitc
manifest. It's also necessary to use both manifests to sync since the
information required to update the gitc manifest is actually in the repo
manifest.

This also fixes a few issues that came up when testing. notdefault
groups weren't being saved to the gitc manifest in a method that matched
'sync'. The merge branch wasn't always being set to the correct value
either.

Change-Id: I435235cb5622a048ffad0059affd32ecf71f1f5b
2015-09-09 20:50:40 -07:00
5cc384034d Merge "Revert "GITC: Always update the gitc manifest from the repo manifest"" 2015-09-09 21:44:11 +00:00
0375523331 Revert "GITC: Always update the gitc manifest from the repo manifest"
This reverts commit 250303b437.

Change-Id: I1fd8af20f802553151aacb953c913f3305ca6057
2015-09-09 21:43:32 +00:00
c32ba1961e Merge "_CopyAndLinkFiles even if the sources haven't changed" 2015-09-09 20:47:19 +00:00
250303b437 GITC: Always update the gitc manifest from the repo manifest
This way any changes made to the main manifest are reflected in the gitc
manifest. It's also necessary to use both manifests to sync since the
information required to update the gitc manifest is actually in the repo
manifest.

This also fixes a few issues that came up when testing. notdefault
groups weren't being saved to the gitc manifest in a method that matched
'sync'. The merge branch wasn't always being set to the correct value
either.

Change-Id: I5dbc850dd73a9fbd10ab2470ae4c40e46ff894de
2015-09-09 12:35:56 -07:00
029eaf3bac _CopyAndLinkFiles even if the sources haven't changed
The source or destination attributes may have changed even if the source
didn't, so we need to make sure that these are up to date.

Change-Id: I266ef3598ddda7e8c23bc9c6a049905ddc586348
2015-09-03 12:54:06 -07:00
ba72d8301e GITC: Fix repo sync.
Fixing http://b/23785024 by calling os.getcwd() because variable
cwd no longer exists.

Change-Id: I21ff7d059e072f9f60726db76b67587a92c878ad
2015-09-03 10:47:44 -07:00
fee390eea2 launcher: Update repo after applying clone.bundle
If the clone.bundle is out of date, repo may be installed with an old
version. It will upgrade with the next sync a day later, or when "repo
selfupdate" is run.

This behavior was added to normal project downloads, but was never added
to the repo launcher.

Change-Id: Ib04bef3a658c98fe1b6c53b3e8d0067165a5e3f7
2015-09-02 12:45:19 -07:00
9ff2ece6ab gitc: Improve help visibility
This improves the visiblity of gitc-init if we can get the gitc config,
and hides it otherwise.

Change-Id: I82830b0b07c311e8c74397ba79eb4c361f8b6fb5
2015-09-01 12:23:56 -07:00
2487cb7b2c Fix gitc check if gitc isn't installed
This was doing cwd.startswith(''), which is always true.

Change-Id: Icc059c09492b31e2d7651e4a595bda783c5abc47
2015-08-31 15:59:54 -07:00
8ce5041596 GITC: Pull GITC Manifest Dir from the config.
Updates the repo launcher and gitc_utils to pull the manifest
directory location out of the gitc config file.

Change-Id: Id08381b8a7d61962093d5cddcb3ff6afbb13004b
2015-08-31 21:39:17 +00:00
f7a51898d3 GITC: Expand relative remote URLs.
The GITC filesystem does not understand relative URLs for remotes,
so now if a remote uses a relative URL, it will be be expanded to
be relative to the manifest URL.

Change-Id: Ie1210758560aeb1934da3f71496aaf19c2728214
2015-08-28 18:09:05 +00:00
b9a1b73425 GITC: Add repo start support.
Add repo start support for GITC checkouts. If the user is in
the GITC FS view, they can now run repo start to check out
the sources and create a new working branch.

When "repo start" is called on a GITC project, the revision
tag is set to an empty string and saved in a new tag:
old-revision. This tells the GITC filesystem to display the
local copy of the sources when being viewed. The local copy
is created by pulling the project sources and the new branch
is created based off the original project revision.

Updated main.py to setup each command's gitc_manifest when
appropriate.

Updated repo sync's logic to sync opened projects and
updating the GITC manifest file for the rest.

Change-Id: I7e4809d1c4fc43c69b26f2f1bebe45aab0cae628
2015-08-28 10:53:05 -07:00
dc2545cad6 project.py: Improve message shown when hook is not replaced
If a hook file has been modified locally, it will not be replaced.

Improve the message to make this clearer.

Also change it from an error to a warning.

Change-Id: I62c635390f24d2868db17717c247861b0381c99f
2015-08-25 05:40:46 +00:00
f33929d014 project.py: Consistently use the _error method to print error messages
Use the _error method instead of directly calling `print`.

Also add a new _warn convenience method.

Change-Id: Ia332c14ef8d9d1fe2df128dbf36b5521802ccdf1
2015-08-25 14:39:06 +09:00
3010e5ba64 Smartsync: Don't fail if there isn't a cookiefile
Change-Id: I434a259f43ca9808e88051ac8ba865c519a24702
2015-08-20 10:29:37 -07:00
ba7bc738c1 Sync: Refactor netrc parsing
Don't emit a message when the netrc file doesn't exist or couldn't
be opened.

Instead of trying to unpack the result of info.authenticators() and
catching the resulting TypeError when it's None, first store it to
a local and only unpack it if it has a value.

Also remove an unused import.

Change-Id: I5c404d91e48c261c1ab850c3e5f040c4f4c235cb
2015-08-20 17:05:17 +09:00
f4599a2a3d gitc_init: Remove unused import
Also add missing newline at the end of the file.

Change-Id: I206e6c4b033d223eb0ff5824ecbf6fd98c39c918
2015-08-20 16:45:39 +09:00
022a1d4e6e gitc_utils: Fix incorrect string format argument
Change-Id: Ibbac6e111833c8f5d93cb6cb4a10f8f2c4fd8e11
2015-08-20 16:41:04 +09:00
41d1baac31 gitc_utils: Remove unused variable
Change-Id: I569819675a99ff6c01fb57b23fed033c39d14d1f
2015-08-20 16:40:44 +09:00
46496d8761 gitc_utils: Remove unused import; add missing import
shutils is not used. Remove it.

sys is used, but not imported.  Import it.

Change-Id: I4ee582f33bbd451601097ef2f20bee23b54764cd
2015-08-20 16:37:09 +09:00
7c9263bce0 Merge "Support smart-sync through persistent-http[s]" 2015-08-19 23:36:35 +00:00
dab9e99f0f Merge "Fix formatting of message when retrying clone" 2015-08-19 17:54:50 +00:00
c5f15bf7c0 Merge "Include project path in --force-sync error message" 2015-08-19 17:53:28 +00:00
6d35d676db Merge "Copy clone-depth in repo manifest" 2015-08-19 17:50:15 +00:00
0745bb2657 Support smart-sync through persistent-http[s]
Use the same cookies and proxy that git traffic goes through for
persistent-http[s] to support authentication for smart-sync.

Change-Id: I20f4a281c259053a5a4fdbc48b1bca48e781c692
2015-08-19 10:22:11 -07:00
25857b8988 Fix formatting of message when retrying clone
Passing the force_sync variable into the string formatting results in
the message:

  "Retrying clone after deleting None"

or

  "Retrying clone after deleting True".

Pass the name of the git directory instead.

Also, move the print inside the if-block so it's only displayed
when the retry is actually going to be attempted.

Change-Id: I76d9ecc176cecee4ad512d13e9d1f6bd36aacbbb
2015-08-19 13:03:13 +00:00
bdb5271de3 GITC: Add repo sync support.
Add repo sync support for GITC checkouts. If the user is in the
GITC client directory they can still pull the sources as normal
if they pass in the --force-gitc argument. Otherwise the user
should call repo sync in the GITC view to update the user's
remote view. (This works because .repo in the GITC view will
link to .repo in the client config directory.)

Part of the support for this change is the refactoring of GITC
related code into gitc_utils.py.

Change-Id: I2636aaa50b450b6f091309db8dd0e8f4dbdad579
2015-08-18 11:59:10 -07:00
884092225d Copy clone-depth in repo manifest
This argument wasn't being copied, which caused syncs from generated
manifests to pull down too much of the git history.

Change-Id: I269bab788d4557267c081628b3f8c6aec7744e81
2015-08-17 15:30:27 -07:00
5d0c3a614e Merge "GITC: Add gitc-init subcommand to repo." 2015-08-12 23:25:20 +00:00
1efc2b4a01 GITC: Add gitc-init subcommand to repo.
Adds the new gitc-init command to set up a GITC client. Gitc-init
sets up the client directory and calls repo init within it. Once
the repo is initialized, then generates a GITC manifest file
by using git ls-remote on each project and retrieving the HEAD SHA
to use as the revision attribute.

Gitc-init inherits from and has all the options as repo init.

Change-Id: Icd7e47e90eab752a77de7c80ebc98cfe16bf6de3
2015-08-12 16:22:14 -07:00
d3ddcdbd8a Ignore clone.bundle on HTTP 501, i.e. Not Implemented
Change-Id: I03ee003d3bd5d0684a31bdf7961a55a511dfa0e2
2015-08-12 20:12:51 +02:00
2635c0e3b6 Merge "Fix shallow clone behavior" 2015-08-05 01:02:41 +00:00
43322283dc Merge "Support filtering by group on forall and list subcmd" 2015-08-05 01:01:02 +00:00
f9b7683a3b Include project path in --force-sync error message
For projects that have been cloned outside of the repo command (or
cloned a long time ago), commit abaa7f312f
introduced an error message to invite the user to use --force-sync.
However, due to the risk of data loss, it's useful to know which
project's git directory is being replaced before deciding whether or not
to provide --force-sync.

This change updates the exception's associated value to include the
project's relative path and explain to the user how they can resolve the
issue. A previous version of this commit used the project name. However,
for projects that have multiple work trees, the name can be ambiguous,
while the path clearly identifies which git directory will be replaced.

Change-Id: If717e66fda4d19accc0a8e889a91f4cd4ff14dff
2015-08-04 18:41:20 -04:00
eeab6860f1 Fix shallow clone behavior
The existing code here makes sure that switching clone-depth from on to
off actually causes the history to be fully restored. Unfortunately, it
does this by fetching the full history every time the fetch spec
changes. Switching between two clone-depth="1" branches will fetch far
more than the top commit.

Instead, when not using clone-depth, pass --depth=2147483647 to git
fetch so that it ensures that we have the entire history. That is
slightly less efficient, so limit it to only when there are shallow
objects in the project by checking for the existance of the 'shallow'
file.

Change-Id: Iee0cfc9c6992c208344b1d9123769992412db67b
2015-08-03 16:54:16 -07:00
7e59de2bcc Include dest-branch attribute in the 'manifest' subcommand's output
Change-Id: If4227d02005fddea82d9e698a373222100d8f710
2015-07-31 17:36:28 -04:00
163fdbf2fd Merge "Fix _ReferenceGitDir symlinking" 2015-07-31 18:01:32 +00:00
555be54790 Merge "Emit project info in case of sync exception." 2015-07-31 17:06:23 +00:00
c5cd433daf Emit project info in case of sync exception.
Previously repo would only print the failing project path if
Sync_NetworkHalf returned false/empty, but if it threw an
exception the print() was never called.

Change-Id: I58c41de43930df5e34b21561c205e062a72e290f
2015-07-31 14:03:50 +00:00
2a3e15217a Fix _ReferenceGitDir symlinking
This fixes these errors:

  ...
  File ".repo/repo/project.py", line 2371, in _ReferenceGitDir
    os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
  OSError: [Errno 17] File exists

Which was happening for checkouts that were created before v1.12.8, when
project-objects was created. Nothing had yet been forcing these
checkouts to use project-objects, until the recent verification changes.

In this OSError case, we already created the symlink, so src == dst, and
the directory did not exist. This caused us to run os.makedirs the
os.symlink on the same file.

dst really should be the file in gitdir, not the target of that symlink
if it exists. So just use realpath for the dotgit portion of the path.

Change-Id: Iff5396a2093de91029c42cf38aa57131fd22981c
2015-07-30 21:29:53 -07:00
0369a069ad Support filtering by group on forall and list subcmd
Enable operating against groups of repositories. As it stands, it isn't
compatible with `-r/--regex`.

`repo forall -g groupname -c pwd` will  run `pwd` for all projects in
groupname.

`repo forall -g thisgroup,-butnotthisone -c pwd` will  run `pwd` for all
projects in `thisgroup` but not `butnotthisone`.

`repo list -g groupname -n` will list all the names of repos in
`groupname`.

Change-Id: Ia75c50ce52541d1c8cea2874b20a4db2e0e54960
2015-07-30 12:59:35 -05:00
abaa7f312f Add option to correct gitdir when syncing
In some cases, a user may wish to continue with a sync even though
it would require overwriting an existing git directory. This behavior
is not safe as a default because it could result in the loss of some
user data, but as an optional flag it allows the user more flexibility.

To support this, add a --force-sync flag to the sync command that will
attempt to overwrite the existing git dir if it is specified and the
existing git dir points to the wrong obj dir.

Change-Id: Ieddda8ad54e264a1eb4a9d54881dd6ebc8a03833
2015-07-29 14:44:46 -06:00
7cccfb2cf0 Merge "InitGitDir: Clean up created directories" 2015-07-29 18:49:12 +00:00
57f43f4944 Merge "Prevent repo info from crashing when default element doesn't exist." 2015-07-29 02:11:55 +00:00
17af578d72 Prevent repo info from crashing when default element doesn't exist.
repo info will crash when using a manifest with no default element despite
default being an optional element. Output nothing for "Manifest Branch" if no
default element exists (or if no default revision exists).

Change-Id: I7ebffa2408863837ba980f0ab6e593134400aea9
2015-07-27 16:56:31 -07:00
b1a07b8276 InitGitDir: Clean up created directories
If _InitGitDir fails, it leaves any progress it had made on the file
system. This can cause subsequent calls to repo sync to behave
differently. This is especially evident when _CheckDirReference() fails,
since it will not be invoked when sync is retried because both the
source and destination directories already exist.

To address this, have _InitGitDir() clean up any directories it has created
if it catches an exception. Also behave the same way for _InitWorkTree().

Change-Id: Ic16bb3feea649e115b59bd44be294e89e3692aeb
2015-07-27 13:33:43 -06:00
4e16c24981 Revert "Add --prune option to fetch when syncing a mirror repo"
For some users it is not desirable to remove refs that don't exist
on the remote server when syncing a mirror repo.

This reverts commit b4d43b9f66.

Change-Id: Ie849b66682138ef88da6cd1a5fbb27e993197dd7
2015-07-20 22:31:04 +09:00
b3d6e67196 Merge "Fail if gitdir does not point to objdir during sync" 2015-07-15 19:30:41 +00:00
503d66d8af Merge "project.RemoteFetch: Handle depth cases more robustly" 2015-07-15 19:29:14 +00:00
679bac4bf3 project.RemoteFetch: Handle depth cases more robustly
The fetch logic for the case where depth is set and revision is a
SHA1 has several failure modes that are not handled well by the
current logic.

1) 'git fetch <SHA1>' requires git version >= 1.8.3
2) 'git fetch <SHA1>' can be prevented by a configuration option on the server.
3) 'git fetch --depth=<N> <refspec>' can fail to contain a SHA1 specified by
   the manifest.

Each of these cases cause infinite recursion when _RemoteFetch() tries to call
itself with current_branch_only=False because current_branch_only is set to
True when depth != None.

To try to prevent the infinite recursion, we set self.clone_depth to None
before the first retry of _RemoteFetch(). This will allow the Fetch to
eventually succeed in the case where clone-depth is specified in the manifest.
A user specified depth from the init command will still recurse infinitely.

In addition, never try to fetch a SHA1 directly if the git version being used
is not at least 1.8.3.

Change-Id: I802fc17878c0929cfd63fff611633c1d3b54ecd3
2015-07-15 15:53:14 +00:00
97836cf09f Merge "Always output upstream if specified" 2015-07-13 16:36:28 +00:00
80e3a37ab5 Merge changes Iaefcbe14,I697a0f64,I19bfe9fe,I06e942c4
* changes:
  forall: use smart sync override manifest if it exists
  sync: Remove smart sync override manifest when not in smart sync mode
  forall: Don't try to get lrev of projects in mirror workspace
  sync: Improve error message when writing smart sync manifest fails
2015-07-11 14:01:16 +00:00
bb4a1b5274 Merge "Improve error message when syncing a project with invalid groups." 2015-07-10 22:00:47 +00:00
551dfecea9 Always output upstream if specified
Previously, in running the `manifest` command, we wouldn't output the
upstream if the default upstream would include the pinned sha1.
However, now that fetching refs/heads/* doesn't guarantee that we will
have the sha1, we need to always output the specified upstream branch.

Change-Id: Ib8b409a8ecd439397b38ee9649c530407797f841
2015-07-10 14:59:10 -07:00
6944cdb8d1 forall: use smart sync override manifest if it exists
If a workspace is synced with the -s or -t option, the included projects
may be different to those in the original manifest. However, when using
the forall command, the list of the projects from the original manifest
is used.

If the smart sync manifest file exists, use it to override the original
manifest.

Change-Id: Iaefcbe148d2158ac046f158d98bbd8b5a5378ce7
2015-07-06 16:18:06 +09:00
59b417493e sync: Remove smart sync override manifest when not in smart sync mode
When syncing with the -s or -t option, a smart_sync_override.xml file
is created. This file is left in the file system when syncing again
without the -s or -t option.

Remove the smart sync override manifest, if it exists, when not using
the -s or -t option.

Change-Id: I697a0f6405205ba5f84a4d470becf7cd23c07b4b
2015-07-06 16:18:06 +09:00
30d13eea86 forall: Don't try to get lrev of projects in mirror workspace
git rev-parse fails for projects that don't have an explicit revision
specified, and don't have a branch of the same name as the default
revision. This can be the case in a workspace synced with the smart
sync (-s) or smart tag (-t) option.

Change-Id: I19bfe9fe7396170379415d85f10f6440dc6ea08f
2015-07-06 16:18:06 +09:00
727cc3e324 sync: Improve error message when writing smart sync manifest fails
The error message only states that writing the manifest failed.

Include the exception message, so it's easier to track down the reason
that the write failed.

Change-Id: I06e942c48a19521ba45292199519dd0a8bdb1de7
2015-07-06 16:18:06 +09:00
c5ceeb1625 Merge "Fix 'repo cherry-pick' to avoid hanging on commit-msg update." 2015-06-25 14:53:46 +00:00
db75704bfc Fix 'repo cherry-pick' to avoid hanging on commit-msg update.
After performing the actual cherry-pick operation, the code
in cherry_pick.py opens a pipe to 'git commit -F' to rewrite the commit
message, emits the fixed-up commit msg to the pipe, then waits
for 'git commit' to complete. The child 'git' process winds up
hanging while reading from the pipe, however, since the parent
process still has it open. To fix the hang, change the parent process
to close its end of the pipe after it has emitted the message.

Change-Id: I5929371e69a5b076f09009d00d40a2c72ac8ac33
2015-06-22 08:00:20 -04:00
87ea5913f2 Improve error message when syncing a project with invalid groups.
Change-Id: Iaf5c2a0f00667dc09bcf455cfe2f39bfbaa2bfc0
2015-06-19 15:55:15 -07:00
185307d1dd Merge "Teach _LinkFile._Link to handle globs." 2015-06-09 00:14:13 +00:00
c116f94261 forall: setenv, only encode val if encode exists
Change-Id: I655e3043d0118c4e929897d3a51e5e013e5758dc
2015-06-04 00:34:19 +00:00
7993f3cdda init: don't call urllib.parse
it's actually urllib.parse.urlparse

Change-Id: Ie3532e54625e887c8682d92b932ea21a629e8d60
2015-06-04 00:33:33 +00:00
b1d1fd778d git_config: fix _SaveJson typo
Change-Id: I35ca2b3733e6d1508669f9a6690c6645c582912e
2015-06-04 00:22:23 +00:00
be4456cf24 error: fix typos
Change-Id: I09c47024ef54c360ea3c15c5d4f169e13444e412
2015-06-04 00:21:16 +00:00
cf738ed4a1 git_command: only decode when needed
strings no longer need decoding, since unicode is str

Change-Id: I9516d298fee7ddc058452394b7759327fe3aa7a8
2015-06-03 16:50:39 +01:00
6cfc68e1e6 decode the buffer before appending
output from a process is in bytes in python3. we need
to decode it.

in Python3, bytes don't have an encode attribute. use this
to identify it.

Change-Id: I152f2ec34614131027db680ead98b53f9b321ed5
2015-06-03 16:39:32 +01:00
4c426ef1d4 Teach _LinkFile._Link to handle globs.
This allows a project to use globs in the linkfile src attribute. When
a glob is used in the src the dest field must be a directory. Then
_LinkFile._Link(self) calls will create symbolic links in the dest
directory to all of the entries in the src as defined by the glob
specification.

Below all of the entries in master-configs/ will have symbolic links
in <root dir>/configs directory:

  <project name="helloworld.git" path="apps/helloworld">
      <linkfile src="master-configs/*" dest="configs"/>
  </project>

Change-Id: Idfed8fa47c83d2ca6e2b8e867731b8e2f9e2eb47
2015-06-03 08:05:17 -07:00
472ce9f5fa Merge changes I32da12c2,Ie4a65b3e
* changes:
  Skip sleep and retry if git remote update exits with a signal
  Catch exceptions in project list generator
2015-06-02 00:14:43 +00:00
0184dcc510 Make linkfile symlinks relative
The source (target) of the symlink is specified relative to a project
within a tree, and the destination is specified relative to the top
of the tree, so it should always be possible to create a relative symlink
to the target file.  Relative symlinks will allow moving an entire tree
without breaking the symlink, and copying a tree (with -p) without leaving
a symlink to the old tree.

Change-Id: I16492a8b59a137d2abe43ca78e3b212e2c835599
2015-06-01 01:24:38 +00:00
c4b301f988 Skip sleep and retry if git remote update exits with a signal
Pressing ctrl-c during repo sync often hangs for 30 to 45 seconds
due to the time.sleep and retry in _RemoteFetch.  If git exits with
a signal, for example -2 for SIGINT triggered by ctrl-c, skip the
sleep and retry.

Change-Id: I32da12c2dcc96d9cc0b12a066e824b12ebfb52a0
2015-05-13 18:11:34 +00:00
31a7be561e Catch exceptions in project list generator
If the generator that produces per-project worker arguments raises an
exception it triggers python bug http://bugs.python.org/issue8296.
Rewrite the generator expression as a generator function, and catch
Exceptions and KeyboardInterrupts to end the iteration.

Also add a pool worker initializer to disable SIGINT to prevent
KeyboardInterrupts inside multiprocessing.Pool in the worker threads
causing the same problem.

Fixes easy-to-reproduce hangs when hitting ctrl-c during
repo forall -c echo

Change-Id: Ie4a65b3e1e07a64ed6bb6ff20f3912c4326718ca
2015-05-13 11:09:38 -07:00
384b3c5948 Fail if gitdir does not point to objdir during sync
There are a set of cases that can cause the git directory in
.repo/projects to point to a directory in .repo/project-objects that
is not the one specified in the manifest. This results in a tree that
is not sane, and so should cause a failure.

In order to reproduce the failure case:
1) Sync to any manifest
2) Change the 'name' of a project to a different repository. Leave the
   'path' the same.
3) Resync the modified project. The project-objects directory will not
   be created, and the projects directory will remain pointed at the old
   project-objects.

Change-Id: Ie6711b1c773508850c5c9f748a27ff72d65e2bf2
2015-05-12 09:15:53 -06:00
35de228f33 Merge "Don't attempt to create "fully qualified names" for SHA1s" 2015-05-11 09:20:54 +00:00
ace097c36e Merge "Add option on sync to avoid fetching from remotes for existing sha1" 2015-05-01 07:51:52 +00:00
b155354034 Add option on sync to avoid fetching from remotes for existing sha1
In 2fb6466f79 an optimisation was
added to avoid fetching from remotes if the project is fixed to
a revision and the revision is already available locally.

This causes problems for users who expect all objects to be
fetched by default.

Change the logic so that the optimized behaviour is only enabled if
an option is explicitly given to repo sync.

Change-Id: I3b2794ddd8e0071b1787e166463cd8347ca9e24f
2015-04-30 14:29:02 +00:00
382582728e Don't attempt to create "fully qualified names" for SHA1s
Doing so breaks "repo init -b <SHA1>".

Change-Id: Ic071a1b099a9125db22ea446d7e92e7854d69b37
2015-04-30 14:54:47 +02:00
b4d43b9f66 Add --prune option to fetch when syncing a mirror repo
When syncing a mirror repo, add the --prune option to the fetch
command to force removal of stale refs from the mirror.

Change-Id: I4b43b2a5c86b9915627887c16f6569066f3ab978
2015-04-30 10:32:37 +09:00
4ccad7554b Fix substitution err for schemeless manifest urls
Previously, we used a regex that would only remove a phony string from
a url if it existed, but we recently replaced that with a slice.  This
change goes back to the previous behavior.

Change-Id: I8baf527be01c4b49d45b903b31a1cd6315563d5b
2015-04-29 10:45:37 -07:00
403b64edf4 Don't append branch to fetch spec when syncing to a mirror
Appending the branch to the fetch spec causes sync of a mirror to
fail for projects that don't have an explicit revision specified,
and don't have a branch of the same name as the default revision.

For example, a manifest defining a default revision:

 <default revision="master">

having a project without an explicit revision:

 <project name="path/to/project">

and not having a branch named "master", will cause repo sync to
fail for that project with the error:

 Couldn't find remote ref refs/heads/master

Modify the logic to not append the branch onto the fetch spec when
syncing to a mirror.

Change-Id: I5c4457bd125519abf27abe682dea62ad708978c9
2015-04-27 10:56:27 +09:00
a38769cda8 Merge "forall: use a generator to map the Pool" 2015-04-08 17:59:58 +00:00
44859d0267 Merge "status: lose dependence on StringIO" 2015-04-08 17:58:35 +00:00
6ad6dbefe7 forall: use a generator to map the Pool
Before, a list was generated, which is why there was a massive delay.

Using a generator will allow processes to start straight away

Change-Id: Ia325b0b340cc328c08c9bcc92a6709bbdaf6a664
2015-04-08 13:22:34 +01:00
33fe4e99f9 Remove deprecated include-ids setting from pylint config
Change-Id: Ie5ab21e434d24ff862bb5e0c263761370d71f56f
2015-04-07 11:10:17 +09:00
4214585073 Merge "Pylint and PEP8 fixes for color.py" 2015-04-07 02:06:49 +00:00
b51f07cd06 status: lose dependence on StringIO
buflist was being used, which isn't available in Python 3.

`Execute` was using StringIO to capture the output of `PrintWorkTreeStatus`,
only to redirect it straight to stdout.
Instead, just let `PrintWorkTreeStatus` do it's own thing directly to stdout.

for handling `_FindOrphans`, we swap StringIO for a list. Nothing was done
that needed a a file like object.

Change-Id: Ibdaae137904de66a5ffb590d84203ef0fe782d8b
2015-04-04 21:21:49 +01:00
04f2f0e186 Maintain fully qualified tracking branches
When running repo branch, the git merge line (in many circumstances)
is set to the revision of the project specified in the manifest.  If
this is a branch name that is not fully-qualified, we will end up with
something like "merge = master" instead of "merge = refs/heads/master".
This change examines the revision if we are going to use that and
changes branch short names to fully qualified branch names.

Change-Id: Ie1be94fb8d45df8eeac44a47f729a3819a05fa81
2015-04-01 17:43:36 +00:00
cb07ba7e3d Resolve fetch urls more efficiently
Instead of using regex, append the netloc and relative
scheme lists with the custom scheme.
The schemes will only be appended when needed, instead
of passing X amount of regex replaces.

see http://bugs.python.org/issue18828 for more details.

Change-Id: I10d26d5ddc32e7ed04c5a412bdd6e13ec59eb70f
2015-03-31 20:12:44 +00:00
23ff7df6a7 use the max depth instead of unshallow
This allows the use of older versions of git

Change-Id: I88ea685066603af19896a791829355ddbfa91ffe
2015-03-30 21:54:26 +00:00
cc1b1a703d Revert "Change the min git version from 1.7.2 to 1.8.2"
This reverts commit 52b99aa91d.

Change-Id: I01d93704c92f7af1ca2b36dbc9509ee1290e2d3c
2015-03-30 21:53:25 +00:00
bdf7ed2301 Pylint and PEP8 fixes for color.py
Change-Id: I1a676e25957a7b5dd800d2585a2ec7fe75295668
2015-03-28 21:12:27 +00:00
9c76f67f13 Always capture output for GitCommand
Switch the GitCommand program to always capture the output for stdout
and stderr.  And by default print the output while running.

The options capture_stdout and capture_stderr have effectively become
options to supress the printing of stdout and stderr.

Update the 'git fetch' to use '--progress' so that the progress messages
will be displayed.  git checks if the output location isatty() and if it
is not a TTY it will by default not print the progress messages.

Change-Id: Ifdae138e008f80a59195f9f43c911a1a5210ec60
2015-03-26 11:43:55 -07:00
52b99aa91d Change the min git version from 1.7.2 to 1.8.2
This is needed for the --unshallow option of git fetch.

Change-Id: Ifdc5cec6130315c643924328fea425f1b94cb04a
2015-03-18 21:43:39 +00:00
9371979628 Revert "Implementation of manifest defined githooks"
This reverts commit 38e4387f8e.

A "repo init" followed by "repo sync" is meant to be as safe as
"git clone".  In particular it should not run arbitrary code provided
by the manifest owner.

It would still be nice to have support for manifest-defined git hooks
--- they'd just need a prompt like the upload RepoHook has.  Hopefully
a later change can bring them back.

Change-Id: I5ecd90fb5c2ed64f103d856d1ffcba38a47b062d
Signed-off-by: Jonathan Nieder <jrn@google.com>
2015-03-17 11:29:58 -07:00
2086004261 Merge "Don't exit with error on HTTP 401 when downloading clone bundle" 2015-03-11 17:25:45 +00:00
2338788050 Don't exit with error on HTTP 401 when downloading clone bundle
If the server returns HTTP 401 (unauthorized) when attempting to
download clone bundle files, ignore it and continue, rather than
exiting with a fatal error.

Change-Id: I2c7ee03e149c354c7e4ad6ea1ebf266534778fe1
2015-03-11 07:43:40 +00:00
0402cd882a Add space between project path and branch in repo status.
Currently, paths longer than 39 chars have no space after them so it looks
like this:

project path/branch master

Change-Id: I4c1bb13648ac099ade8a8d4ebafa04131571f842
2015-03-11 07:42:17 +00:00
936183a492 git_config: add support for remote '.'
As a fix for issue #149, this patch add support for the remote '.'
(local).

As an alias for the local repository, remote '.' is lacking a fetch =
config in .git/config.

Without such refspec, repo info --overview is not able to process a
local tracking branch.

v2: Check for name == '.' before checking if merge starts with refs/,
    since the case where it's not is invalid.

Signed-off-by: Yann Droneaud <ydroneaud@opteya.com>
Signed-off-by: Filipe Brandenburger <filbranden@google.com>

Change-Id: I8c8fd8602cd68baecb530301ae41d37d751ec85d
2015-03-06 13:23:27 -08:00
85e8267031 Merge "Implementation of manifest defined githooks" 2015-03-05 20:52:30 +00:00
e30f46b957 Print stderr output from git command for RemoteFetch
The stderr output generated by git during a RemoteFetch was not being
printed.  This information is useful so print it.

Change-Id: I6e6ce12c4a57e5ca2359f76ce14f2fcbbc37a5ef
2015-02-25 14:29:28 -08:00
e4978cfbe3 Ensure the repo project is never fetched with partial depth
If the repo project is synced with partial depth, then the tags
won't be fetched and users will be told the newest sha1 in the
stable branch isn't signed.

Change-Id: I107df97b4836b928c76aa33a700fa35d1705ae09
2015-02-10 14:44:05 -08:00
126e298214 Handle case where 'git remote prune' needs to be run
Handle the case when this error occurs:
    error: some local refs could not be updated; try running
     'git remote prune origin' to remove any old, conflicting branches

This is usually caused by a reference getting changed from a file to a
directory.

For example:
  Initially someone creates a branch 'foo' and it is stored as:
    .git/refs/remotes/origin/foo

  Then later on it is decided to change the layout structure where 'foo'
  is a directory with branches below it:
    .git/refs/remotes/origin/foo/master

  The problem occurs when someone still has
  '.git/refs/remotes/origin/foo' on their system and does a repo sync.
  When this occurs the error message for needing to do a
  'git remote prune origin' occurs.

Now when doing a 'git fetch' if the error message from git says that a
'git remote prune' is needed, it will do the prune and then retry the
fetch.

Change-Id: I4c6f5aa6bd932f0ef7a39134400bedd52e82f633
Signed-off-by: John L. Villalovos <john.l.villalovos@intel.com>
2015-02-03 13:49:51 -08:00
38e4387f8e Implementation of manifest defined githooks
When working within a team or corporation it is often
useful/required to use predefined git templates. This
change teaches repo to use a per-remote git hook template
structure.

The implementation is done as a continuation of the
existing projecthook functionality. The terminology is
therefore defined as projecthooks.

The downloaded projecthooks are stored in the .repo
directory as a metaproject separating them from the users
project forest.

The projecthooks are downloaded and set up when doing a
repo init and updated for each new repo init.

When downloading a mirror the projecthooks gits are
not added to the bare forest since the intention is to
ensure that the latest are used (allows for company policy
enforcement).

The projecthooks are defined in the manifest file in the
remote element as a subnode, the name refers to the
project name on the server referred to in the remote.
<remote name="myremote ..>
   <projecthook name="myprojecthookgit" revision="myrevision"/>
</remote>

The hooks found in the projecthook revision supersede
the stock hooks found in repo. This removes the need for
updating the projecthook gits for repo stock hook changes.

Change-Id: I6796b7b0342c1f83c35f4b3e46782581b069a561
Signed-off-by: Patrik Ryd <patrik.ryd@stericsson.com>
Signed-off-by: Ian Kumlien <ian.kumlien@gmail.com>
2015-02-03 16:01:15 +09:00
24245e0094 Merge "Add missing documentation of --current-branch option on sync command" 2015-01-31 12:44:45 +00:00
db6f1b0884 Merge "Use depth flag when fetching" 2015-01-30 19:36:06 +00:00
f2fad61bde Add missing documentation of --current-branch option on sync command
Change-Id: I72d6e3d51241148c1df97bbad26338debb1fcb4e
2015-01-29 14:36:28 +09:00
ee69084421 Merge "Handle shallow checkout of SHA1 pinned repos" 2015-01-28 20:29:37 +00:00
d37d43f036 Merge "Don't delete hooks in .git/hooks" 2015-01-28 20:29:05 +00:00
7bdac71087 pylint fixes for project.py
Fix all the formatting warnings and unused variables

Change-Id: I17d88a23572303879530077f3a80451de5417fbb
2015-01-22 04:20:21 +00:00
f97e8383a3 Use depth flag when fetching
Currently, we only use the depth flag when cloning.  The result is that when
new project history has merges, the entire history of the merged branch is
brought in and the project becomes unshallow very quickly.  --depth and
clone-depth are often used to save on space, not just network load, so this
seems less than ideal.

This change uses --depth on every fetch (when the user has depth specified),
not just the initial clone.  The result is that the given project stays
consistently shallow as opposed to growing over time, especially when merges
are involved.

Change-Id: Iac706cfdad4a555c72f9d9f1119195d38d91df12
2015-01-22 01:20:22 +00:00
3000cdad22 Handle shallow checkout of SHA1 pinned repos
When doing a shallow checkout SHA1 pinned repos with repo init --depth=1 and
repo sync -c, repo would try to fetch only some reference and fail if the exact
SHA1 repo was missing.
Instead, when depth is set, fetch only the specific commit.

Change-Id: If3f799d0e78c03faea47f796380bb5e367b11998
2015-01-21 14:14:23 -08:00
b9d9efd394 Don't delete hooks in .git/hooks
We currently delete all hooks in .git/hooks for each project before
symlink'ing in the standard project hooks.  This can be annoying for
users who have installed custom git hooks.

There's no reason to delete all existing hooks.  Just rip out the
deletion code.

Change-Id: I5062a6cd20af700f6d6a17b11ad6c94853987c57
Signed-off-by: Mitchel Humpherys <mitchelh@codeaurora.org>
2015-01-15 22:49:08 -08:00
497bde4de5 Respect --quiet when looking up bundle cookie file
Change-Id: I02a244132c49e4bb50ecda978974d6d2b220f6d1
2015-01-02 13:58:05 -08:00
4abf8e6ef8 Save cookies back to jar when fetching clone.bundle
Change-Id: I3ef71b5e7f8ee1cda66057e46ae234866c7258c4
2015-01-02 13:57:14 -08:00
137d0131bf Hold persistent proxy connection open while fetching clone.bundle
The persistent proxy may choose to present a per-process cookie file
that gets cleaned up after the process exits, to help with the fact
that libcurl cannot save cookies atomically when a cookie file is
shared across processes. We were letting this cleanup happen
immediately by closing stdin as soon as we read the configuration
option, resulting in a nonexistent cookie file by the time we use the
config option.

Work around this by converting the cookie logic to a context manager
method, which closes the process only when we're done with the cookie
file.

Change-Id: I12a88b25cc19621ef8161337144c1b264264211a
2015-01-02 13:57:13 -08:00
42e679b9f6 Merge "add a global --color option" 2015-01-02 20:56:25 +00:00
902665bce6 add a global --color option
If you want to turn off colors for commands, you have to manually adjust
the git config settings (in various locations).  If you're writing scripts
though, you often don't want to modify those locations.  Add a commandline
option to explicitly control things.

The default behavior is unchanged -- we still scan the config files.

Change-Id: I54a3fd8e1918bac180aadd7c7d3004f069b02522
2014-12-30 18:50:05 -05:00
c8d882ae2a Silence warnings about invalid clone.bundle files when quieted
The invalid clone.bundle file warning is not typically user actionable,
and can be confusing. So don't show it when -q flag is in effect.

Change-Id: If9fef4085391acf54b63c75029ec0e161c38eb86
2014-12-24 10:23:24 +09:00
3eb87cec5c Revert "Check for existence of refs upon initial fetch"
This reverts commit 565480588d.

We are reverting this change for 2 reasons:

1) It introduced a bug for users using sync -c with a reference mirror.
2) The fetch specs have recently changed to cause git to properly fail
when we request a non-existent branch of a manifest, removing the need
for this change.

Change-Id: I0f63da9bfb40cf5ffafb7979f1b8c929a738fc7b
2014-11-10 23:49:32 +00:00
5fb8ed217c If revision is sha hash and dest-branch is defined, use it for starting branch
Change-Id: I538c7d216f72b87629b61aee547d374a398c95da
2014-10-27 12:25:05 +00:00
7e12e0a2fa Support persistent-http(s) review urls
Change-Id: I8e0065685c968dfa9dc26bcdb6ee2fa14019c509
2014-10-23 15:42:09 -07:00
7893b85509 Merge changes I1f71be22,I5b119f11
* changes:
  Always fetch the specific revision given
  Support specifying non-HEADS refs as upstream
2014-10-22 00:23:18 +00:00
b4e50e67e8 Merge "upload: report names of uncommitted files" 2014-10-21 18:03:55 +00:00
0936aeab2c Exit 1 if repo download -c fails
Change-Id: I6985548bf87032b121eeccf858c4eeca1a60598c
2014-10-17 15:45:57 -04:00
14e134da02 upload: report names of uncommitted files
When there are uncommitted files in the tree, 'repo upload' stops to
ask if it is OK to continue, but does not report the actual names of
uncommitted files.

This patch adds plumbing to have the outstanding file names reported
if desired.

BUG=None
TEST=verified that 'repo upload' properly operates with the following
    conditions present in the tree:
    . file(s) modified locally
    . file(s) added to index, but not committed
    . files not known to git
    . no modified files (the upload proceeds as expected)

Change-Id: If65d5f8e8bcb3300c16d85dc5d7017758545f80d
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Signed-off-by: Vadim Bendebury <vbendeb@google.com>
2014-10-14 11:20:05 -07:00
04e52d6166 Always fetch the specific revision given
Don't assume the revision is in refs/heads/.

Change-Id: I1f71be222ed3ed940d2265aad43d1f2d601fc03a
2014-10-09 13:41:56 -06:00
909d58b2e2 Support specifying non-HEADS refs as upstream
While not typical, some users might have an upstream that isn't in
the usual refs/heads/* namespace. There's no reason not to use
those refs as the value for the upstream attribute, so support
doing so.

Change-Id: I5b119f1135c3268c20e7c4084682e860d3ee1fb1
2014-10-09 13:41:51 -06:00
5cf16607d3 Allow selection of a target when using smart sync.
Change-Id: I02a24471b9b62dbba3773f22a289825bc566acd9
2014-10-02 10:17:44 -07:00
c190b98ed5 Merge "Add extend-project tag to support adding groups to an existing project" 2014-09-18 23:09:08 +00:00
4863307299 Add support for rpc:// protocol schemes.
Change-Id: I0e500e45cacc20ac04b43435c4bd189299e9e97b
2014-09-10 13:45:52 -07:00
f75870beac Change implementation of cleanup in case of clone failure during "repo init"
Fix includes:
1. It deletes only .repo/repo instead of the whole .repo repository.

Bug: Issue 161
Change-Id: I1ab8caa7538fec5e6206d1b029f63bd3f60dedcd
2014-09-03 13:56:04 +05:30
bf0b0cbc2f Merge "Provide detail print-out when not all projects of a branch are current." 2014-08-26 21:11:40 +00:00
3a10968a70 Merge "Enable transferring of attribute using command 'repo manifest -o -'" 2014-08-22 16:13:16 +00:00
c46de6932a Decode git version
Used by 'repo --version'
With Python 3,
* Before: b'git version 2.1.0'
* After: git version 2.1.0

Change-Id: I4321bb0f09e92cda1123c35910338b940e82a305
2014-08-20 11:47:10 +05:30
303a82f33a Don't open non-binary files as binary
* Don't pen the git config file, and the git ".lock" file as binary.

Change-Id: I7b3939658456f2fd0a0500443cdd8d1ee1a4459d
2014-08-19 23:05:44 +05:30
7a91d51dcf Enable transferring of attribute using command 'repo manifest -o -'
'upstream' attribute is now transferred to the new manifest xml
that is created when using command 'repo manifest -o -'.

Manifest help is updated for the attributes 'sync-c','sync-s' and
'sync-j'.

Bug: Issue 164
Change-Id: If63f781e91d25c5b5b5ea0696b0c04337b0a686a
2014-07-24 16:27:08 +05:30
a8d539189e Update the commit-msg hook to the version from Gerrit 2.8.2
Change-Id: Id911bc6841f488a42d08580de800c3afafa2937e
2014-07-15 11:30:06 -07:00
588142dfcb Provide detail print-out when not all projects of a branch are current.
When current is "split" (i.e. some projects are current while others are not):
- Disable 'not in' printout (i.e. will print out all projects)
- Disable printing of multiple projects on one line
- Print current projects in green, non-current in white

Since using color to differentiate current from non-current in "split" cases:
- In non-split cases also print out project names in color (green for current
  white for non-current)

Change-Id: Ia6b826612c708447cecfe5954dc767f7b2ea2ea7
2014-07-11 10:56:03 -07:00
a6d258b84d Merge "Fix UrlInsteadOf to handle multiple strings" 2014-06-30 22:21:58 +00:00
a769498568 Add --jobs option to forall subcommand
Enable '--jobs' ('-j') option in the forall subcommand. For -jn
where n > 1, the '-p' option can no longer guarantee the
continuity of console output between the project header and the
output from the worker process.

SIG_INT is sent to all worker processes upon keyboard interrupt
(Ctrl+C).

Bug: Issue 105
Change-Id: If09afa2ed639d481ede64f28b641dc80d0b89a5c
2014-06-24 01:02:54 +00:00
884a387eca Add extend-project tag to support adding groups to an existing project
Currently, if a local manifest wants to add groups to an existing
project, it must use remove-project and then re-add the project with
the new groups.  This makes the local manifest more fragile, requiring
updates to the local manifest if the original manifest changes.

Add a new extend-project tag, which supports adding groups to an
existing project.

Change-Id: Ib4d1352efd722a65dd263d02644b9ea5ab6ed400
2014-06-20 11:35:16 -07:00
80b87fe6c1 Use fetch --unshallow when appropriate.
If a user reinits to a different manifest or the manifest updates so
that a project no longer has a fixed depth, we need to use --unshallow
when we fetch.

Change-Id: I6d3f15e5464b5eaad9205654bc24354947a78aea
2014-05-09 18:47:35 -07:00
e9f75b1782 Merge "Enable remotes to define their own revision" 2014-05-08 18:38:33 +00:00
a35e402161 Merge "Return a list rather than dict_values in XmlManifest.projects()" 2014-05-07 18:21:31 +00:00
dd7aea6c11 Merge "Define unicode as str if using Python 3" 2014-05-07 18:20:32 +00:00
5196805fa2 Merge "Use exec() rather than execfile()" 2014-05-07 18:18:56 +00:00
85b24acd6a Use JSON instead of pickle
Use JSON as it is shown to be much faster than pickle.
Also clean up the loading and saving functions.

Change-Id: I45b3dee7b4d59a1c0e0d38d4a83b543ac5839390
2014-05-07 10:46:24 +01:00
36ea2fb6ee Enable remotes to define their own revision
Some projects use multiple remotes.
In some cases these remotes have different naming conventions.
Add an option to define a revision in the remote configuration.

The `project` revision takes precedence over `remote` and `default`.
The `remote` revision takes precedence over `default`.
The `default` revision acts as a fall back as it originally did.

Change-Id: I2b376160d45d48b0bab840c02a3eef1a1e32cf6d
2014-05-07 08:29:30 +00:00
2cd1f0452e Use next(iterator) rather than iterator.next()
iterator.next() was replaced with iterator.__next__() in Python 3.
Use next(iterator) instead which will select the correct method for
returning the next item.

Change-Id: I6d0c89c8b32e817e5897fe87332933dacf22027b
2014-05-07 08:44:20 +01:00
65e3a78a9e Merge "Prevent warning twice about Python 3 usage" 2014-05-07 06:30:30 +00:00
d792f7928d Define unicode as str if using Python 3
The unicode object was renamed to str in Python 3

Change-Id: I1e4972fb07b313d3462587b3059bb3638d779625
2014-05-06 20:38:51 +01:00
6efdde9f6e Prevent warning twice about Python 3 usage
Only warn about using Python 3 when running the repo script directly.
This prevents the user being warned twice.

Change-Id: I2ee51ea2fa0127ea310598320e460ec9f38c6488
2014-05-06 12:44:22 +00:00
7446c5954a Use sorted() rather than .sort()
dict.keys() produces a dict_keys object in Python 3, which does
not support .sort(). Use sorted() which will give the same outcome.

Change-Id: If6b33db07a31995b4e44959209d08d8fb74ae339
2014-05-06 12:42:35 +00:00
d58bfe5a58 Return a list rather than dict_values in XmlManifest.projects()
dict.values() produce dict_values objects rather than list objects.
Convert this to a list to maintain functionality with certain functions.

Change-Id: Ie76269e19f8d68479a1d7ae03aa965252d759a9e
2014-05-06 09:16:52 +01:00
70f6890352 Use exec() rather than execfile()
execfile() is not in Python 3.

Change-Id: I5af222340f13c1e8edaa820e7675d3e4d62a1689
2014-05-05 23:41:07 +01:00
666d534636 Ensure HEAD is correct when skipping remote fetch
A recent optimization (2fb6466f79) skips
performing a remote fetch if we already know we have the sha1 we want.
However, that optimization skipped initialization steps that ensure HEAD
points to the correct sha1.  This change makes sure not to skip those
steps.

Here is an example of how to test this change:

"""""""""
url=<manifest url>
branch1=<branch name>
branch2=<branch name>
project=<project with revision set to different sha1 in each branch>

repo init -u $url -b $branch1 --mirror
repo sync $project
first=$(cd $project.git; git rev-parse HEAD)

repo init -b $branch2
repo sync $project
second=$(cd platform/build.git; git rev-parse HEAD)

if [[ $first == $second ]]
then
    echo 'problem!'
else
    echo 'no problem!'
fi
"""""""""
2014-05-01 13:20:32 -07:00
4e4d40f7c0 Fix UrlInsteadOf to handle multiple strings
For complex .gitconfig url rewrites, multiple insteadOf lines may be
used for a url. Search all of them for the right rewrite.

Change-Id: If5e9ecd054e86226924b0baf513801cd57c389cd
2014-03-06 21:04:18 -08:00
132 changed files with 20637 additions and 4683 deletions

15
.flake8 Normal file
View File

@ -0,0 +1,15 @@
[flake8]
max-line-length=100
ignore=
# E111: Indentation is not a multiple of four
E111,
# E114: Indentation is not a multiple of four (comment)
E114,
# E402: Module level import not at top of file
E402,
# E731: do not assign a lambda expression, use a def
E731,
# W503: Line break before binary operator
W503,
# W504: Line break after binary operator
W504

2
.gitattributes vendored
View File

@ -1,4 +1,4 @@
# Prevent /bin/sh scripts from being clobbered by autocrlf=true
git_ssh text eol=lf
main.py text eol=lf
repo text eol=lf
hooks/* text eol=lf

31
.github/workflows/test-ci.yml vendored Normal file
View File

@ -0,0 +1,31 @@
# GitHub actions workflow.
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions
name: Test CI
on:
push:
branches: [main, repo-1, stable, maint]
tags: [v*]
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.6, 3.7, 3.8, 3.9]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Test with tox
run: tox

10
.gitignore vendored
View File

@ -1,3 +1,13 @@
*.asc
*.egg-info/
*.log
*.pyc
__pycache__
/dist
.repopickle_*
/repoc
/.tox
/.venv
# PyCharm related
/.idea/

5
.gitreview Normal file
View File

@ -0,0 +1,5 @@
[gerrit]
host=gerrit-review.googlesource.com
scheme=https
project=git-repo.git
defaultbranch=main

13
.mailmap Normal file
View File

@ -0,0 +1,13 @@
Anthony Newnam <anthony.newnam@garmin.com> Anthony <anthony@bnovc.com>
He Ping <tdihp@hotmail.com> heping <tdihp@hotmail.com>
Hu Xiuyun <xiuyun.hu@hisilicon.com> Hu xiuyun <xiuyun.hu@hisilicon.com>
Hu Xiuyun <xiuyun.hu@hisilicon.com> Hu Xiuyun <clouds08@qq.com>
Jelly Chen <chenguodong@huawei.com> chenguodong <chenguodong@huawei.com>
Jia Bi <bijia@xiaomi.com> bijia <bijia@xiaomi.com>
Jiri Tyr <jiri.tyr@gmail.com> Jiri tyr <jiri.tyr@gmail.com>
JoonCheol Park <jooncheol@gmail.com> Jooncheol Park <jooncheol@gmail.com>
Sergii Pylypenko <x.pelya.x@gmail.com> pelya <x.pelya.x@gmail.com>
Shawn Pearce <sop@google.com> Shawn O. Pearce <sop@google.com>
Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjolin <ulrik.sjolin@gmail.com>
Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjolin <ulrik.sjolin@sonyericsson.com>
Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Ulrik Sjölin <ulrik.sjolin@sonyericsson.com>

View File

@ -5,6 +5,6 @@
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/git-repo</path>
</pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
</pydev_project>

301
.pylintrc
View File

@ -1,301 +0,0 @@
# lint Python modules using external checkers.
#
# This is the main checker controling the other ones and the reports
# generation. It is itself both a raw checker and an astng checker in order
# to:
# * handle message activation / deactivation at the module level
# * handle some basic but necessary stats'data (number of classes, methods...)
#
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=SVN
# Pickle collected data for later comparisons.
persistent=yes
# Set the cache size for astng objects.
cache-size=500
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable only checker(s) with the given id(s). This option conflicts with the
# disable-checker option
#enable-checker=
# Enable all checker(s) except those with the given id(s). This option
# conflicts with the enable-checker option
#disable-checker=
# Enable all messages in the listed categories.
#enable-msg-cat=
# Disable all messages in the listed categories.
#disable-msg-cat=
# Enable the message(s) with the given id(s).
enable=RP0004
# Disable the message(s) with the given id(s).
disable=R0903,R0912,R0913,R0914,R0915,W0141,C0111,C0103,W0603,W0703,R0911,C0301,C0302,R0902,R0904,W0142,W0212,E1101,E1103,R0201,W0201,W0122,W0232,RP0001,RP0003,RP0101,RP0002,RP0401,RP0701,RP0801,F0401,E0611,R0801,I0011
[REPORTS]
# set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=text
# Include message's id in output
include-ids=yes
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note).You have access to the variables errors warning, statement which
# respectivly contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (R0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (R0004).
comment=no
# checks for
# * unused variables / imports
# * undefined variables
# * redefinition of variable from builtins or from an outer scope
# * use of variable before assigment
#
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching names used for dummy variables (i.e. not used).
dummy-variables-rgx=_|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# try to find bugs in the code using type inference
#
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamicaly set).
ignored-classes=SQLObject
# When zope mode is activated, consider the acquired-members option to ignore
# access to some undefined attributes.
zope=no
# List of members which are usually get through zope's acquisition mecanism and
# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
acquired-members=REQUEST,acl_users,aq_parent
# checks for :
# * doc strings
# * modules / classes / functions / methods / arguments / variables name
# * number of arguments, local variables, branchs, returns and statements in
# functions, methods
# * required module attributes
# * dangerous default values as arguments
# * redefinition of function / method / class
# * uses of the global statement
#
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# Regular expression which should only match functions or classes name which do
# not require a docstring
no-docstring-rgx=_main|__.*__
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))|(log)$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_,e,d1,d2,v,f,l,d
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# checks for sign of poor/misdesign:
# * number of methods, attributes, local variables...
# * size, complexity of functions, methods
#
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branchs=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=20
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=30
# checks for
# * external modules dependencies
# * relative / wildcard imports
# * cyclic imports
# * uses of deprecated modules
#
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report R0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report R0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report R0402 must
# not be disabled)
int-import-graph=
# checks for :
# * methods without self as first argument
# * overridden methods signature
# * access only to existant members via self
# * attributes not defined in the __init__ method
# * supported interfaces implementation
# * unreachable code
#
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# checks for similarities and duplicated code. This computation may be
# memory / CPU intensive, so you should disable it if you experiments some
# problems.
#
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# checks for:
# * warning notes in the code like FIXME, XXX
# * PEP 263: source code with non ascii character but no encoding declaration
#
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
# checks for :
# * unauthorized constructions
# * strict indentation
# * line length
# * use of <> instead of !=
#
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab). In repo it is 2 spaces.
indent-string=' '

View File

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
graft docs hooks tests
include *.py
include LICENSE
include git_ssh
include repo
include run_tests

55
README.md Normal file
View File

@ -0,0 +1,55 @@
# repo
Repo is a tool built on top of Git. Repo helps manage many Git repositories,
does the uploads to revision control systems, and automates parts of the
development workflow. Repo is not meant to replace Git, only to make it
easier to work with Git. The repo command is an executable Python script
that you can put anywhere in your path.
* Homepage: <https://gerrit.googlesource.com/git-repo/>
* Mailing list: [repo-discuss on Google Groups][repo-discuss]
* Bug reports: <https://bugs.chromium.org/p/gerrit/issues/list?q=component:repo>
* Source: <https://gerrit.googlesource.com/git-repo/>
* Overview: <https://source.android.com/source/developing.html>
* Docs: <https://source.android.com/source/using-repo.html>
* [repo Manifest Format](./docs/manifest-format.md)
* [repo Hooks](./docs/repo-hooks.md)
* [Submitting patches](./SUBMITTING_PATCHES.md)
* Running Repo in [Microsoft Windows](./docs/windows.md)
* GitHub mirror: <https://github.com/GerritCodeReview/git-repo>
* Postsubmit tests: <https://github.com/GerritCodeReview/git-repo/actions>
## Contact
Please use the [repo-discuss] mailing list or [issue tracker] for questions.
You can [file a new bug report][new-bug] under the "repo" component.
Please do not e-mail individual developers for support.
They do not have the bandwidth for it, and often times questions have already
been asked on [repo-discuss] or bugs posted to the [issue tracker].
So please search those sites first.
## Install
Many distros include repo, so you might be able to install from there.
```sh
# Debian/Ubuntu.
$ sudo apt-get install repo
# Gentoo.
$ sudo emerge dev-vcs/repo
```
You can install it manually as well as it's a single script.
```sh
$ mkdir -p ~/.bin
$ PATH="${HOME}/.bin:${PATH}"
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
$ chmod a+rx ~/.bin/repo
```
[new-bug]: https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue
[issue tracker]: https://bugs.chromium.org/p/gerrit/issues/list?q=component:repo
[repo-discuss]: https://groups.google.com/forum/#!forum/repo-discuss

View File

@ -1,87 +0,0 @@
Short Version:
- Make small logical changes.
- Provide a meaningful commit message.
- Check for coding errors with pylint
- Make sure all code is under the Apache License, 2.0.
- Publish your changes for review:
git push https://gerrit-review.googlesource.com/git-repo HEAD:refs/for/master
Long Version:
I wanted a file describing how to submit patches for repo,
so I started with the one found in the core Git distribution
(Documentation/SubmittingPatches), which itself was based on the
patch submission guidelines for the Linux kernel.
However there are some differences, so please review and familiarize
yourself with the following relevant bits:
(1) Make separate commits for logically separate changes.
Unless your patch is really trivial, you should not be sending
out a patch that was generated between your working tree and your
commit head. Instead, always make a commit with complete commit
message and generate a series of patches from your repository.
It is a good discipline.
Describe the technical detail of the change(s).
If your description starts to get too long, that's a sign that you
probably need to split up your commit to finer grained pieces.
(2) Check for coding errors with pylint
Run pylint on changed modules using the provided configuration:
pylint --rcfile=.pylintrc file.py
(3) Check the license
repo is licensed under the Apache License, 2.0.
Because of this licensing model *every* file within the project
*must* list the license that covers it in the header of the file.
Any new contributions to an existing file *must* be submitted under
the current license of that file. Any new files *must* clearly
indicate which license they are provided under in the file header.
Please verify that you are legally allowed and willing to submit your
changes under the license covering each file *prior* to submitting
your patch. It is virtually impossible to remove a patch once it
has been applied and pushed out.
(4) Sending your patches.
Do not email your patches to anyone.
Instead, login to the Gerrit Code Review tool at:
https://gerrit-review.googlesource.com/
Ensure you have completed one of the necessary contributor
agreements, providing documentation to the project maintainers that
they have right to redistribute your work under the Apache License:
https://gerrit-review.googlesource.com/#/settings/agreements
Ensure you have obtained an HTTP password to authenticate:
https://gerrit-review.googlesource.com/new-password
Push your patches over HTTPS to the review server, possibly through
a remembered remote to make this easier in the future:
git config remote.review.url https://gerrit-review.googlesource.com/git-repo
git config remote.review.push HEAD:refs/for/master
git push review
You will be automatically emailed a copy of your commits, and any
comments made by the project maintainers.

177
SUBMITTING_PATCHES.md Normal file
View File

@ -0,0 +1,177 @@
[TOC]
# Short Version
- Make small logical changes.
- [Provide a meaningful commit message][commit-message-style].
- Check for coding errors and style nits with flake8.
- Make sure all code is under the Apache License, 2.0.
- Publish your changes for review.
- Make corrections if requested.
- Verify your changes on gerrit so they can be submitted.
`git push https://gerrit-review.googlesource.com/git-repo HEAD:refs/for/main`
# Long Version
I wanted a file describing how to submit patches for repo,
so I started with the one found in the core Git distribution
(Documentation/SubmittingPatches), which itself was based on the
patch submission guidelines for the Linux kernel.
However there are some differences, so please review and familiarize
yourself with the following relevant bits.
## Make separate commits for logically separate changes.
Unless your patch is really trivial, you should not be sending out a patch that
was generated between your working tree and your commit head.
Instead, always make a commit with a complete
[commit message][commit-message-style] and generate a series of patches from
your repository.
It is a good discipline.
Describe the technical detail of the change(s).
If your description starts to get too long, that's a sign that you
probably need to split up your commit to finer grained pieces.
## Check for coding errors and style violations with flake8
Run `flake8` on changed modules:
flake8 file.py
Note that repo generally follows [Google's Python Style Guide] rather than
[PEP 8], with a couple of notable exceptions:
* Indentation is at 2 columns rather than 4
* The maximum line length is 100 columns rather than 80
There should be no new errors or warnings introduced.
Warnings that cannot be avoided without going against the Google Style Guide
may be suppressed inline individally using a `# noqa` comment as described
in the [flake8 documentation].
If there are many occurrences of the same warning, these may be suppressed for
the entire project in the included `.flake8` file.
[Google's Python Style Guide]: https://google.github.io/styleguide/pyguide.html
[PEP 8]: https://www.python.org/dev/peps/pep-0008/
[flake8 documentation]: https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html#in-line-ignoring-errors
## Running tests
We use [pytest](https://pytest.org/) and [tox](https://tox.readthedocs.io/) for
running tests. You should make sure to install those first.
To run the full suite against all supported Python versions, simply execute:
```sh
$ tox -p auto
```
We have [`./run_tests`](./run_tests) which is a simple wrapper around `pytest`:
```sh
# Run the full suite against the default Python version.
$ ./run_tests
# List each test as it runs.
$ ./run_tests -v
# Run a specific unittest module (and all tests in it).
$ ./run_tests tests/test_git_command.py
# Run a specific testsuite in a specific unittest module.
$ ./run_tests tests/test_editor.py::EditString
# Run a single test.
$ ./run_tests tests/test_editor.py::EditString::test_cat_editor
# List all available tests.
$ ./run_tests --collect-only
# Run a single test using substring match.
$ ./run_tests -k test_cat_editor
```
The coverage isn't great currently, but it should still be run for all commits.
Adding more unittests for changes you make would be greatly appreciated :).
Check out the [tests/](./tests/) subdirectory for more details.
## Check the license
repo is licensed under the Apache License, 2.0.
Because of this licensing model *every* file within the project
*must* list the license that covers it in the header of the file.
Any new contributions to an existing file *must* be submitted under
the current license of that file. Any new files *must* clearly
indicate which license they are provided under in the file header.
Please verify that you are legally allowed and willing to submit your
changes under the license covering each file *prior* to submitting
your patch. It is virtually impossible to remove a patch once it
has been applied and pushed out.
## Sending your patches.
Do not email your patches to anyone.
Instead, login to the Gerrit Code Review tool at:
https://gerrit-review.googlesource.com/
Ensure you have completed one of the necessary contributor
agreements, providing documentation to the project maintainers that
they have right to redistribute your work under the Apache License:
https://gerrit-review.googlesource.com/#/settings/agreements
Ensure you have obtained an HTTP password to authenticate:
https://gerrit-review.googlesource.com/new-password
Ensure that you have the local commit hook installed to automatically
add a ChangeId to your commits:
curl -Lo `git rev-parse --git-dir`/hooks/commit-msg https://gerrit-review.googlesource.com/tools/hooks/commit-msg
chmod +x `git rev-parse --git-dir`/hooks/commit-msg
If you have already committed your changes you will need to amend the commit
to get the ChangeId added.
git commit --amend
Push your patches over HTTPS to the review server, possibly through
a remembered remote to make this easier in the future:
git config remote.review.url https://gerrit-review.googlesource.com/git-repo
git config remote.review.push HEAD:refs/for/main
git push review
You will be automatically emailed a copy of your commits, and any
comments made by the project maintainers.
## Make changes if requested
The project maintainer who reviews your changes might request changes to your
commit. If you make the requested changes you will need to amend your commit
and push it to the review server again.
## Verify your changes on gerrit
After you receive a Code-Review+2 from the maintainer, select the Verified
button on the gerrit page for the change. This verifies that you have tested
your changes and notifies the maintainer that they are ready to be submitted.
The maintainer will then submit your changes to the repository.
[commit-message-style]: https://chris.beams.io/posts/git-commit/

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -18,41 +17,43 @@ import sys
import pager
COLORS = {None :-1,
'normal' :-1,
'black' : 0,
'red' : 1,
'green' : 2,
'yellow' : 3,
'blue' : 4,
COLORS = {None: -1,
'normal': -1,
'black': 0,
'red': 1,
'green': 2,
'yellow': 3,
'blue': 4,
'magenta': 5,
'cyan' : 6,
'white' : 7}
'cyan': 6,
'white': 7}
ATTRS = {None :-1,
'bold' : 1,
'dim' : 2,
'ul' : 4,
'blink' : 5,
ATTRS = {None: -1,
'bold': 1,
'dim': 2,
'ul': 4,
'blink': 5,
'reverse': 7}
RESET = "\033[m" # pylint: disable=W1401
# backslash is not anomalous
RESET = "\033[m"
def is_color(s):
return s in COLORS
def is_attr(s):
return s in ATTRS
def _Color(fg = None, bg = None, attr = None):
def _Color(fg=None, bg=None, attr=None):
fg = COLORS[fg]
bg = COLORS[bg]
attr = ATTRS[attr]
if attr >= 0 or fg >= 0 or bg >= 0:
need_sep = False
code = "\033[" #pylint: disable=W1401
code = "\033["
if attr >= 0:
code += chr(ord('0') + attr)
@ -71,7 +72,6 @@ def _Color(fg = None, bg = None, attr = None):
if bg >= 0:
if need_sep:
code += ';'
need_sep = True
if bg < 8:
code += '4%c' % (ord('0') + bg)
@ -83,15 +83,39 @@ def _Color(fg = None, bg = None, attr = None):
return code
DEFAULT = None
def SetDefaultColoring(state):
"""Set coloring behavior to |state|.
This is useful for overriding config options via the command line.
"""
if state is None:
# Leave it alone -- return quick!
return
global DEFAULT
state = state.lower()
if state in ('auto',):
DEFAULT = state
elif state in ('always', 'yes', 'true', True):
DEFAULT = 'always'
elif state in ('never', 'no', 'false', False):
DEFAULT = 'never'
class Coloring(object):
def __init__(self, config, section_type):
self._section = 'color.%s' % section_type
self._config = config
self._out = sys.stdout
on = self._config.GetString(self._section)
on = DEFAULT
if on is None:
on = self._config.GetString('color.ui')
on = self._config.GetString(self._section)
if on is None:
on = self._config.GetString('color.ui')
if on == 'auto':
if pager.active or os.isatty(1):
@ -122,6 +146,7 @@ class Coloring(object):
def printer(self, opt=None, fg=None, bg=None, attr=None):
s = self
c = self.colorer(opt, fg, bg, attr)
def f(fmt, *args):
s._out.write(c(fmt, *args))
return f
@ -129,6 +154,7 @@ class Coloring(object):
def nofmt_printer(self, opt=None, fg=None, bg=None, attr=None):
s = self
c = self.nofmt_colorer(opt, fg, bg, attr)
def f(fmt):
s._out.write(c(fmt))
return f
@ -136,11 +162,13 @@ class Coloring(object):
def colorer(self, opt=None, fg=None, bg=None, attr=None):
if self._on:
c = self._parse(opt, fg, bg, attr)
def f(fmt, *args):
output = fmt % args
return ''.join([c, output, RESET])
return f
else:
def f(fmt, *args):
return fmt % args
return f
@ -148,6 +176,7 @@ class Coloring(object):
def nofmt_colorer(self, opt=None, fg=None, bg=None, attr=None):
if self._on:
c = self._parse(opt, fg, bg, attr)
def f(fmt):
return ''.join([c, fmt, RESET])
return f

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,25 +12,75 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import multiprocessing
import os
import optparse
import platform
import re
import sys
from event_log import EventLog
from error import NoSuchProjectError
from error import InvalidProjectGroupsError
import progress
# Are we generating man-pages?
GENERATE_MANPAGES = os.environ.get('_REPO_GENERATE_MANPAGES_') == ' indeed! '
# Number of projects to submit to a single worker process at a time.
# This number represents a tradeoff between the overhead of IPC and finer
# grained opportunity for parallelism. This particular value was chosen by
# iterating through powers of two until the overall performance no longer
# improved. The performance of this batch size is not a function of the
# number of cores on the system.
WORKER_BATCH_SIZE = 32
# How many jobs to run in parallel by default? This assumes the jobs are
# largely I/O bound and do not hit the network.
DEFAULT_LOCAL_JOBS = min(os.cpu_count(), 8)
class Command(object):
"""Base class for any command line action in repo.
"""
common = False
manifest = None
_optparse = None
# Singleton for all commands to track overall repo command execution and
# provide event summary to callers. Only used by sync subcommand currently.
#
# NB: This is being replaced by git trace2 events. See git_trace2_event_log.
event_log = EventLog()
def WantPager(self, opt):
# Whether this command is a "common" one, i.e. whether the user would commonly
# use it or it's a more uncommon command. This is used by the help command to
# show short-vs-full summaries.
COMMON = False
# Whether this command supports running in parallel. If greater than 0,
# it is the number of parallel jobs to default to.
PARALLEL_JOBS = None
# Whether this command supports Multi-manifest. If False, then main.py will
# iterate over the manifests and invoke the command once per (sub)manifest.
# This is only checked after calling ValidateOptions, so that partially
# migrated subcommands can set it to False.
MULTI_MANIFEST_SUPPORT = True
def __init__(self, repodir=None, client=None, manifest=None, gitc_manifest=None,
git_event_log=None, outer_client=None, outer_manifest=None):
self.repodir = repodir
self.client = client
self.outer_client = outer_client or client
self.manifest = manifest
self.gitc_manifest = gitc_manifest
self.git_event_log = git_event_log
self.outer_manifest = outer_manifest
# Cache for the OptionParser property.
self._optparse = None
def WantPager(self, _opt):
return False
def ReadEnvironmentOptions(self, opts):
@ -63,13 +112,50 @@ class Command(object):
usage = self.helpUsage.strip().replace('%prog', me)
except AttributeError:
usage = 'repo %s' % self.NAME
self._optparse = optparse.OptionParser(usage = usage)
epilog = 'Run `repo help %s` to view the detailed manual.' % self.NAME
self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
self._CommonOptions(self._optparse)
self._Options(self._optparse)
return self._optparse
def _Options(self, p):
"""Initialize the option parser.
def _CommonOptions(self, p, opt_v=True):
"""Initialize the option parser with common options.
These will show up for *all* subcommands, so use sparingly.
NB: Keep in sync with repo:InitParser().
"""
g = p.add_option_group('Logging options')
opts = ['-v'] if opt_v else []
g.add_option(*opts, '--verbose',
dest='output_mode', action='store_true',
help='show all output')
g.add_option('-q', '--quiet',
dest='output_mode', action='store_false',
help='only show errors')
if self.PARALLEL_JOBS is not None:
default = 'based on number of CPU cores'
if not GENERATE_MANPAGES:
# Only include active cpu count if we aren't generating man pages.
default = f'%default; {default}'
p.add_option(
'-j', '--jobs',
type=int, default=self.PARALLEL_JOBS,
help=f'number of jobs to run in parallel (default: {default})')
m = p.add_option_group('Multi-manifest options')
m.add_option('--outer-manifest', action='store_true', default=None,
help='operate starting at the outermost manifest')
m.add_option('--no-outer-manifest', dest='outer_manifest',
action='store_false', help='do not operate on outer manifests')
m.add_option('--this-manifest-only', action='store_true', default=None,
help='only operate on this (sub)manifest')
m.add_option('--no-this-manifest-only', '--all-manifests',
dest='this_manifest_only', action='store_false',
help='operate on this manifest and its submanifests')
def _Options(self, p):
"""Initialize the option parser with subcommand-specific options."""
def _RegisteredEnvironmentOptions(self):
"""Get options that can be set from environment variables.
@ -95,30 +181,92 @@ class Command(object):
self.OptionParser.print_usage()
sys.exit(1)
def CommonValidateOptions(self, opt, args):
"""Validate common options."""
opt.quiet = opt.output_mode is False
opt.verbose = opt.output_mode is True
if opt.outer_manifest is None:
# By default, treat multi-manifest instances as a single manifest from
# the user's perspective.
opt.outer_manifest = True
def ValidateOptions(self, opt, args):
"""Validate the user options & arguments before executing.
This is meant to help break the code up into logical steps. Some tips:
* Use self.OptionParser.error to display CLI related errors.
* Adjust opt member defaults as makes sense.
* Adjust the args list, but do so inplace so the caller sees updates.
* Try to avoid updating self state. Leave that to Execute.
"""
def Execute(self, opt, args):
"""Perform the action, after option parsing is complete.
"""
raise NotImplementedError
@staticmethod
def ExecuteInParallel(jobs, func, inputs, callback, output=None, ordered=False):
"""Helper for managing parallel execution boiler plate.
For subcommands that can easily split their work up.
Args:
jobs: How many parallel processes to use.
func: The function to apply to each of the |inputs|. Usually a
functools.partial for wrapping additional arguments. It will be run
in a separate process, so it must be pickalable, so nested functions
won't work. Methods on the subcommand Command class should work.
inputs: The list of items to process. Must be a list.
callback: The function to pass the results to for processing. It will be
executed in the main thread and process the results of |func| as they
become available. Thus it may be a local nested function. Its return
value is passed back directly. It takes three arguments:
- The processing pool (or None with one job).
- The |output| argument.
- An iterator for the results.
output: An output manager. May be progress.Progess or color.Coloring.
ordered: Whether the jobs should be processed in order.
Returns:
The |callback| function's results are returned.
"""
try:
# NB: Multiprocessing is heavy, so don't spin it up for one job.
if len(inputs) == 1 or jobs == 1:
return callback(None, output, (func(x) for x in inputs))
else:
with multiprocessing.Pool(jobs) as pool:
submit = pool.imap if ordered else pool.imap_unordered
return callback(pool, output, submit(func, inputs, chunksize=WORKER_BATCH_SIZE))
finally:
if isinstance(output, progress.Progress):
output.end()
def _ResetPathToProjectMap(self, projects):
self._by_path = dict((p.worktree, p) for p in projects)
def _UpdatePathToProjectMap(self, project):
self._by_path[project.worktree] = project
def _GetProjectByPath(self, path):
def _GetProjectByPath(self, manifest, path):
project = None
if os.path.exists(path):
oldpath = None
while path \
and path != oldpath \
and path != self.manifest.topdir:
while (path and
path != oldpath and
path != manifest.topdir):
try:
project = self._by_path[path]
break
except KeyError:
oldpath = path
path = os.path.dirname(path)
if not project and path == manifest.topdir:
try:
project = self._by_path[path]
except KeyError:
pass
else:
try:
project = self._by_path[path]
@ -126,17 +274,34 @@ class Command(object):
pass
return project
def GetProjects(self, args, missing_ok=False, submodules_ok=False):
def GetProjects(self, args, manifest=None, groups='', missing_ok=False,
submodules_ok=False, all_manifests=False):
"""A list of projects that match the arguments.
Args:
args: a list of (case-insensitive) strings, projects to search for.
manifest: an XmlManifest, the manifest to use, or None for default.
groups: a string, the manifest groups in use.
missing_ok: a boolean, whether to allow missing projects.
submodules_ok: a boolean, whether to allow submodules.
all_manifests: a boolean, if True then all manifests and submanifests are
used. If False, then only the local (sub)manifest is used.
Returns:
A list of matching Project instances.
"""
all_projects_list = self.manifest.projects
if all_manifests:
if not manifest:
manifest = self.manifest.outer_client
all_projects_list = manifest.all_projects
else:
if not manifest:
manifest = self.manifest
all_projects_list = manifest.projects
result = []
mp = self.manifest.manifestProject
groups = mp.config.GetString('manifest.groups')
if not groups:
groups = 'default,platform-' + platform.system().lower()
groups = manifest.GetGroupsStr()
groups = [x for x in re.split(r'[,\s]+', groups) if x]
if not args:
@ -147,29 +312,38 @@ class Command(object):
for p in project.GetDerivedSubprojects())
all_projects_list.extend(derived_projects.values())
for project in all_projects_list:
if ((missing_ok or project.Exists) and
project.MatchesGroups(groups)):
if (missing_ok or project.Exists) and project.MatchesGroups(groups):
result.append(project)
else:
self._ResetPathToProjectMap(all_projects_list)
for arg in args:
projects = self.manifest.GetProjectsWithName(arg)
# We have to filter by manifest groups in case the requested project is
# checked out multiple times or differently based on them.
projects = [project for project in manifest.GetProjectsWithName(
arg, all_manifests=all_manifests)
if project.MatchesGroups(groups)]
if not projects:
path = os.path.abspath(arg).replace('\\', '/')
project = self._GetProjectByPath(path)
tree = manifest
if all_manifests:
# Look for the deepest matching submanifest.
for tree in reversed(list(manifest.all_manifests)):
if path.startswith(tree.topdir):
break
project = self._GetProjectByPath(tree, path)
# If it's not a derived project, update path->project mapping and
# search again, as arg might actually point to a derived subproject.
if (project and not project.Derived and
(submodules_ok or project.sync_s)):
if (project and not project.Derived and (submodules_ok or
project.sync_s)):
search_again = False
for subproject in project.GetDerivedSubprojects():
self._UpdatePathToProjectMap(subproject)
search_again = True
if search_again:
project = self._GetProjectByPath(path) or project
project = self._GetProjectByPath(manifest, path) or project
if project:
projects = [project]
@ -179,7 +353,8 @@ class Command(object):
for project in projects:
if not missing_ok and not project.Exists:
raise NoSuchProjectError(arg)
raise NoSuchProjectError('%s (%s)' % (
arg, project.RelPath(local=not all_manifests)))
if not project.MatchesGroups(groups):
raise InvalidProjectGroupsError(arg)
@ -190,39 +365,80 @@ class Command(object):
result.sort(key=_getpath)
return result
def FindProjects(self, args):
def FindProjects(self, args, inverse=False, all_manifests=False):
"""Find projects from command line arguments.
Args:
args: a list of (case-insensitive) strings, projects to search for.
inverse: a boolean, if True, then projects not matching any |args| are
returned.
all_manifests: a boolean, if True then all manifests and submanifests are
used. If False, then only the local (sub)manifest is used.
"""
result = []
patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
for project in self.GetProjects(''):
for project in self.GetProjects('', all_manifests=all_manifests):
paths = [project.name, project.RelPath(local=not all_manifests)]
for pattern in patterns:
if pattern.search(project.name) or pattern.search(project.relpath):
match = any(pattern.search(x) for x in paths)
if not inverse and match:
result.append(project)
break
result.sort(key=lambda project: project.relpath)
if inverse and match:
break
else:
if inverse:
result.append(project)
result.sort(key=lambda project: (project.manifest.path_prefix,
project.relpath))
return result
# pylint: disable=W0223
# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
# override method `Execute` which is abstract in `Command`. Since that method
# is always implemented in classes derived from `InteractiveCommand` and
# `PagedCommand`, this warning can be suppressed.
def ManifestList(self, opt):
"""Yields all of the manifests to traverse.
Args:
opt: The command options.
"""
top = self.outer_manifest
if not opt.outer_manifest or opt.this_manifest_only:
top = self.manifest
yield top
if not opt.this_manifest_only:
for child in top.all_children:
yield child
class InteractiveCommand(Command):
"""Command which requires user interaction on the tty and
must not run within a pager, even if the user asks to.
"""
def WantPager(self, opt):
def WantPager(self, _opt):
return False
class PagedCommand(Command):
"""Command which defaults to output in a pager, as its
display tends to be larger than one screen full.
"""
def WantPager(self, opt):
def WantPager(self, _opt):
return True
# pylint: enable=W0223
class MirrorSafeCommand(object):
"""Command permits itself to run within a mirror,
and does not require a working directory.
"""
class GitcAvailableCommand(object):
"""Command that requires GITC to be available, but does
not require the local client to be a GITC client.
"""
class GitcClientCommand(object):
"""Command that requires the local client to be a GITC
client.
"""

156
completion.bash Normal file
View File

@ -0,0 +1,156 @@
# Copyright 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Programmable bash completion. https://github.com/scop/bash-completion
# TODO: Handle interspersed options. We handle `repo h<tab>`, but not
# `repo --time h<tab>`.
# Complete the list of repo subcommands.
__complete_repo_list_commands() {
local repo=${COMP_WORDS[0]}
(
# Handle completions if running outside of a checkout.
if ! "${repo}" help --all 2>/dev/null; then
repo help 2>/dev/null
fi
) | sed -n '/^ /{s/ \([^ ]\+\) .\+/\1/;p}'
}
# Complete list of all branches available in all projects in the repo client
# checkout.
__complete_repo_list_branches() {
local repo=${COMP_WORDS[0]}
"${repo}" branches 2>/dev/null | \
sed -n '/|/{s/[ *][Pp ] *\([^ ]\+\) .*/\1/;p}'
}
# Complete list of all projects available in the repo client checkout.
__complete_repo_list_projects() {
local repo=${COMP_WORDS[0]}
"${repo}" list -n 2>/dev/null
"${repo}" list -p --relative-to=. 2>/dev/null
}
# Complete the repo <command> argument.
__complete_repo_command() {
if [[ ${COMP_CWORD} -ne 1 ]]; then
return 1
fi
local command=${COMP_WORDS[1]}
COMPREPLY=($(compgen -W "$(__complete_repo_list_commands)" -- "${command}"))
return 0
}
# Complete repo subcommands that take <branch> <projects>.
__complete_repo_command_branch_projects() {
local current=$1
if [[ ${COMP_CWORD} -eq 2 ]]; then
COMPREPLY=($(compgen -W "$(__complete_repo_list_branches)" -- "${current}"))
else
COMPREPLY=($(compgen -W "$(__complete_repo_list_projects)" -- "${current}"))
fi
}
# Complete repo subcommands that take only <projects>.
__complete_repo_command_projects() {
local current=$1
COMPREPLY=($(compgen -W "$(__complete_repo_list_projects)" -- "${current}"))
}
# Complete `repo help`.
__complete_repo_command_help() {
local current=$1
# CWORD=1 is "start".
# CWORD=2 is the <subcommand> which we complete here.
if [[ ${COMP_CWORD} -eq 2 ]]; then
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_commands)" -- "${current}")
)
fi
}
# Complete `repo forall`.
__complete_repo_command_forall() {
local current=$1
# CWORD=1 is "forall".
# CWORD=2+ are <projects> *until* we hit the -c option.
local i
for (( i = 0; i < COMP_CWORD; ++i )); do
if [[ "${COMP_WORDS[i]}" == "-c" ]]; then
return 0
fi
done
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_projects)" -- "${current}")
)
}
# Complete `repo start`.
__complete_repo_command_start() {
local current=$1
# CWORD=1 is "start".
# CWORD=2 is the <branch> which we don't complete.
# CWORD=3+ are <projects> which we complete here.
if [[ ${COMP_CWORD} -gt 2 ]]; then
COMPREPLY=(
$(compgen -W "$(__complete_repo_list_projects)" -- "${current}")
)
fi
}
# Complete the repo subcommand arguments.
__complete_repo_arg() {
if [[ ${COMP_CWORD} -le 1 ]]; then
return 1
fi
local command=${COMP_WORDS[1]}
local current=${COMP_WORDS[COMP_CWORD]}
case ${command} in
abandon|checkout)
__complete_repo_command_branch_projects "${current}"
return 0
;;
branch|branches|diff|info|list|overview|prune|rebase|smartsync|stage|status|\
sync|upload)
__complete_repo_command_projects "${current}"
return 0
;;
help|start|forall)
__complete_repo_command_${command} "${current}"
return 0
;;
*)
return 1
;;
esac
}
# Complete the repo arguments.
__complete_repo() {
COMPREPLY=()
__complete_repo_command && return 0
__complete_repo_arg && return 0
return 0
}
# Fallback to the default complete methods if we aren't able to provide anything
# useful. This will allow e.g. local paths to be used when it makes sense.
complete -F __complete_repo -o bashdefault -o default repo

269
docs/internal-fs-layout.md Normal file
View File

@ -0,0 +1,269 @@
# Repo internal filesystem layout
A reference to the `.repo/` tree in repo client checkouts.
Hopefully it's complete & up-to-date, but who knows!
*** note
**Warning**:
This is meant for developers of the repo project itself as a quick reference.
**Nothing** in here must be construed as ABI, or that repo itself will never
change its internals in backwards incompatible ways.
***
[TOC]
## .repo/ layout
All content under `.repo/` is managed by `repo` itself with few exceptions.
In general, you should not make manual changes in here.
If a setting was initialized using an option to `repo init`, you should use that
command to change the setting later on.
It is always safe to re-run `repo init` in existing repo client checkouts.
For example, if you want to change the manifest branch, you can simply run
`repo init --manifest-branch=<new name>` and repo will take care of the rest.
* `config`: Per-repo client checkout settings using [git-config] file format.
* `.repo_config.json`: JSON cache of the `config` file for repo to
read/process quickly.
### repo/ state
* `repo/`: A git checkout of the repo project. This is how `repo` re-execs
itself to get the latest released version.
It tracks the git repository at `REPO_URL` using the `REPO_REV` branch.
Those are specified at `repo init` time using the `--repo-url=<REPO_URL>`
and `--repo-rev=<REPO_REV>` options.
Any changes made to this directory will usually be automatically discarded
by repo itself when it checks for updates. If you want to update to the
latest version of repo, use `repo selfupdate` instead. If you want to
change the git URL/branch that this tracks, re-run `repo init` with the new
settings.
* `.repo_fetchtimes.json`: Used by `repo sync` to record stats when syncing
the various projects.
### Manifests
For more documentation on the manifest format, including the local_manifests
support, see the [manifest-format.md] file.
* `submanifests/{submanifest.path}/`: The path prefix to the manifest state of
a submanifest included in a multi-manifest checkout. The outermost manifest
manifest state is found adjacent to `submanifests/`.
* `manifests/`: A git checkout of the manifest project. Its `.git/` state
points to the `manifest.git` bare checkout (see below). It tracks the git
branch specified at `repo init` time via `--manifest-branch`.
The local branch name is always `default` regardless of the remote tracking
branch. Do not get confused if the remote branch is not `default`, or if
there is a remote `default` that is completely different!
No manual changes should be made in here as it will just confuse repo and
it won't automatically recover causing no new changes to be picked up.
* `manifests.git/`: A bare checkout of the manifest project. It tracks the
git repository specified at `repo init` time via `--manifest-url`.
No manual changes should be made in here as it will just confuse repo.
If you want to switch the tracking settings, re-run `repo init` with the
new settings.
* `manifest.xml`: The manifest that repo uses. It is generated at `repo init`
and uses the `--manifest-name` to determine what manifest file to load next
out of `manifests/`.
Do not try to modify this to load other manifests as it will confuse repo.
If you want to switch manifest files, re-run `repo init` with the new
setting.
Older versions of repo managed this with symlinks.
* `manifest.xml -> manifests/<manifest-name>.xml`: A symlink to the manifest
that the user wishes to sync. It is specified at `repo init` time via
`--manifest-name`.
* `manifests.git/.repo_config.json`: JSON cache of the `manifests.git/config`
file for repo to read/process quickly.
* `local_manifest.xml` (*Deprecated*): User-authored tweaks to the manifest
used to sync. See [local manifests] for more details.
* `local_manifests/`: Directory of user-authored manifest fragments to tweak
the manifest used to sync. See [local manifests] for more details.
### Project objects
*** note
**Warning**: Please do not use repo's approach to projects/ & project-objects/
layouts as a model for other tools to implement similar approaches.
It has a number of known downsides like:
* [Symlinks do not work well under Windows](./windows.md).
* Git sometimes replaces symlinks under .git/ with real files (under unknown
circumstances), and then the internal state gets out of sync, and data loss
may ensue.
* When sharing project-objects between multiple project checkouts, Git might
automatically run `gc` or `prune` which may lead to data loss or corruption
(since those operate on leaf projects and miss refs in other leaves). See
https://gerrit-review.googlesource.com/c/git-repo/+/254392 for more details.
Instead, you should use standard Git workflows like [git worktree] or
[gitsubmodules] with [superprojects].
***
* `copy-link-files.json`: Tracking file used by `repo sync` to determine when
copyfile or linkfile are added or removed and need corresponding updates.
* `project.list`: Tracking file used by `repo sync` to determine when projects
are added or removed and need corresponding updates in the checkout.
* `projects/`: Bare checkouts of every project synced by the manifest. The
filesystem layout matches the `<project path=...` setting in the manifest
(i.e. where it's checked out in the repo client source tree). Those
checkouts will symlink their `.git/` state to paths under here.
Some git state is further split out under `project-objects/`.
* `project-objects/`: Git objects that are safe to share across multiple
git checkouts. The filesystem layout matches the `<project name=...`
setting in the manifest (i.e. the path on the remote server) with a `.git`
suffix. This allows for multiple checkouts of the same remote git repo to
share their objects. For example, you could have different branches of
`foo/bar.git` checked out to `foo/bar-main`, `foo/bar-release`, etc...
There will be multiple trees under `projects/` for each one, but only one
under `project-objects/`.
This layout is designed to allow people to sync against different remotes
(e.g. a local mirror & a public review server) while avoiding duplicating
the content. However, this can run into problems if different remotes use
the same path on their respective servers. Best to avoid that.
* `subprojects/`: Like `projects/`, but for git submodules.
* `subproject-objects/`: Like `project-objects/`, but for git submodules.
* `worktrees/`: Bare checkouts of every project synced by the manifest. The
filesystem layout matches the `<project name=...` setting in the manifest
(i.e. the path on the remote server) with a `.git` suffix. This has the
same advantages as the `project-objects/` layout above.
This is used when [git worktree]'s are enabled.
### Global settings
The `.repo/manifests.git/config` file is used to track settings for the entire
repo client checkout.
Most settings use the `[repo]` section to avoid conflicts with git.
Everything under `[repo.syncstate.*]` is used to keep track of sync details for logging
purposes.
User controlled settings are initialized when running `repo init`.
| Setting | `repo init` Option | Use/Meaning |
|------------------- |---------------------------|-------------|
| manifest.groups | `--groups` & `--platform` | The manifest groups to sync |
| manifest.standalone | `--standalone-manifest` | Download manifest as static file instead of creating checkout |
| repo.archive | `--archive` | Use `git archive` for checkouts |
| repo.clonebundle | `--clone-bundle` | Whether the initial sync used clone.bundle explicitly |
| repo.clonefilter | `--clone-filter` | Filter setting when using [partial git clones] |
| repo.depth | `--depth` | Create shallow checkouts when cloning |
| repo.dissociate | `--dissociate` | Dissociate from any reference/mirrors after initial clone |
| repo.git-lfs | `--git-lfs` | Enable [Git LFS] support |
| repo.mirror | `--mirror` | Checkout is a repo mirror |
| repo.partialclone | `--partial-clone` | Create [partial git clones] |
| repo.partialcloneexclude | `--partial-clone-exclude` | Comma-delimited list of project names (not paths) to exclude while using [partial git clones] |
| repo.reference | `--reference` | Reference repo client checkout |
| repo.submodules | `--submodules` | Sync git submodules |
| repo.superproject | `--use-superproject` | Sync [superproject] |
| repo.worktree | `--worktree` | Use [git worktree] for checkouts |
| user.email | `--config-name` | User's e-mail address; Copied into `.git/config` when checking out a new project |
| user.name | `--config-name` | User's name; Copied into `.git/config` when checking out a new project |
[partial git clones]: https://git-scm.com/docs/gitrepository-layout#_code_partialclone_code
[superproject]: https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
### Repo hooks settings
For more details on this feature, see the [repo-hooks docs](./repo-hooks.md).
We'll just discuss the internal configuration settings.
These are stored in the registered `<repo-hooks>` project itself, so if the
manifest switches to a different project, the settings will not be copied.
| Setting | Use/Meaning |
|--------------------------------------|-------------|
| repo.hooks.\<hook\>.approvedmanifest | User approval for secure manifest sources (e.g. https://) |
| repo.hooks.\<hook\>.approvedhash | User approval for insecure manifest sources (e.g. http://) |
For example, if our manifest had the following entries, we would store settings
under `.repo/projects/src/repohooks.git/config` (which would be reachable via
`git --git-dir=src/repohooks/.git config`).
```xml
<project path="src/repohooks" name="chromiumos/repohooks" ... />
<repo-hooks in-project="chromiumos/repohooks" ... />
```
If `<hook>` is `pre-upload`, the `.git/config` setting might be:
```ini
[repo "hooks.pre-upload"]
approvedmanifest = https://chromium.googlesource.com/chromiumos/manifest
```
## Per-project settings
These settings are somewhat meant to be tweaked by the user on a per-project
basis (e.g. `git config` in a checked out source repo).
Where possible, we re-use standard git settings to avoid confusion, and we
refrain from documenting those, so see [git-config] documentation instead.
See `repo help upload` for documentation on `[review]` settings.
The `[remote]` settings are automatically populated/updated from the manifest.
The `[branch]` settings are updated by `repo start` and `git branch`.
| Setting | Subcommands | Use/Meaning |
|-------------------------------|---------------|-------------|
| review.\<url\>.autocopy | upload | Automatically add to `--cc=<value>` |
| review.\<url\>.autoreviewer | upload | Automatically add to `--reviewers=<value>` |
| review.\<url\>.autoupload | upload | Automatically answer "yes" or "no" to all prompts |
| review.\<url\>.uploadhashtags | upload | Automatically add to `--hashtag=<value>` |
| review.\<url\>.uploadlabels | upload | Automatically add to `--label=<value>` |
| review.\<url\>.uploadnotify | upload | [Notify setting][upload-notify] to use |
| review.\<url\>.uploadtopic | upload | Default [topic] to use |
| review.\<url\>.username | upload | Override username with `ssh://` review URIs |
| remote.\<remote\>.fetch | sync | Set of refs to fetch |
| remote.\<remote\>.projectname | \<network\> | The name of the project as it exists in Gerrit review |
| remote.\<remote\>.pushurl | upload | The base URI for pushing CLs |
| remote.\<remote\>.review | upload | The URI of the Gerrit review server |
| remote.\<remote\>.url | sync & upload | The URI of the git project to fetch |
| branch.\<branch\>.merge | sync & upload | The branch to merge & upload & track |
| branch.\<branch\>.remote | sync & upload | The remote to track |
## ~/ dotconfig layout
Repo will create & maintain a few files in the user's home directory.
* `.repoconfig/`: Repo's per-user directory for all random config files/state.
* `.repoconfig/config`: Per-user settings using [git-config] file format.
* `.repoconfig/keyring-version`: Cache file for checking if the gnupg subdir
has all the same keys as the repo launcher. Used to avoid running gpg
constantly as that can be quite slow.
* `.repoconfig/gnupg/`: GnuPG's internal state directory used when repo needs
to run `gpg`. This provides isolation from the user's normal `~/.gnupg/`.
* `.repoconfig/.repo_config.json`: JSON cache of the `.repoconfig/config`
file for repo to read/process quickly.
* `.repo_.gitconfig.json`: JSON cache of the `.gitconfig` file for repo to
read/process quickly.
[git-config]: https://git-scm.com/docs/git-config
[Git LFS]: https://git-lfs.github.com/
[git worktree]: https://git-scm.com/docs/git-worktree
[gitsubmodules]: https://git-scm.com/docs/gitsubmodules
[manifest-format.md]: ./manifest-format.md
[local manifests]: ./manifest-format.md#Local-Manifests
[superprojects]: https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
[topic]: https://gerrit-review.googlesource.com/Documentation/intro-user.html#topics
[upload-notify]: https://gerrit-review.googlesource.com/Documentation/user-upload.html#notify

591
docs/manifest-format.md Normal file
View File

@ -0,0 +1,591 @@
# repo Manifest Format
A repo manifest describes the structure of a repo client; that is
the directories that are visible and where they should be obtained
from with git.
The basic structure of a manifest is a bare Git repository holding
a single `default.xml` XML file in the top level directory.
Manifests are inherently version controlled, since they are kept
within a Git repository. Updates to manifests are automatically
obtained by clients during `repo sync`.
[TOC]
## XML File Format
A manifest XML file (e.g. `default.xml`) roughly conforms to the
following DTD:
```xml
<!DOCTYPE manifest [
<!ELEMENT manifest (notice?,
remote*,
default?,
manifest-server?,
submanifest*?,
remove-project*,
project*,
extend-project*,
repo-hooks?,
superproject?,
contactinfo?,
include*)>
<!ELEMENT notice (#PCDATA)>
<!ELEMENT remote (annotation*)>
<!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED>
<!ATTLIST remote pushurl CDATA #IMPLIED>
<!ATTLIST remote review CDATA #IMPLIED>
<!ATTLIST remote revision CDATA #IMPLIED>
<!ELEMENT default EMPTY>
<!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED>
<!ATTLIST default dest-branch CDATA #IMPLIED>
<!ATTLIST default upstream CDATA #IMPLIED>
<!ATTLIST default sync-j CDATA #IMPLIED>
<!ATTLIST default sync-c CDATA #IMPLIED>
<!ATTLIST default sync-s CDATA #IMPLIED>
<!ATTLIST default sync-tags CDATA #IMPLIED>
<!ELEMENT manifest-server EMPTY>
<!ATTLIST manifest-server url CDATA #REQUIRED>
<!ELEMENT submanifest EMPTY>
<!ATTLIST submanifest name ID #REQUIRED>
<!ATTLIST submanifest remote IDREF #IMPLIED>
<!ATTLIST submanifest project CDATA #IMPLIED>
<!ATTLIST submanifest manifest-name CDATA #IMPLIED>
<!ATTLIST submanifest revision CDATA #IMPLIED>
<!ATTLIST submanifest path CDATA #IMPLIED>
<!ATTLIST submanifest groups CDATA #IMPLIED>
<!ATTLIST submanifest default-groups CDATA #IMPLIED>
<!ELEMENT project (annotation*,
project*,
copyfile*,
linkfile*)>
<!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED>
<!ATTLIST project remote IDREF #IMPLIED>
<!ATTLIST project revision CDATA #IMPLIED>
<!ATTLIST project dest-branch CDATA #IMPLIED>
<!ATTLIST project groups CDATA #IMPLIED>
<!ATTLIST project sync-c CDATA #IMPLIED>
<!ATTLIST project sync-s CDATA #IMPLIED>
<!ATTLIST project sync-tags CDATA #IMPLIED>
<!ATTLIST project upstream CDATA #IMPLIED>
<!ATTLIST project clone-depth CDATA #IMPLIED>
<!ATTLIST project force-path CDATA #IMPLIED>
<!ELEMENT annotation EMPTY>
<!ATTLIST annotation name CDATA #REQUIRED>
<!ATTLIST annotation value CDATA #REQUIRED>
<!ATTLIST annotation keep CDATA "true">
<!ELEMENT copyfile EMPTY>
<!ATTLIST copyfile src CDATA #REQUIRED>
<!ATTLIST copyfile dest CDATA #REQUIRED>
<!ELEMENT linkfile EMPTY>
<!ATTLIST linkfile src CDATA #REQUIRED>
<!ATTLIST linkfile dest CDATA #REQUIRED>
<!ELEMENT extend-project EMPTY>
<!ATTLIST extend-project name CDATA #REQUIRED>
<!ATTLIST extend-project path CDATA #IMPLIED>
<!ATTLIST extend-project dest-path CDATA #IMPLIED>
<!ATTLIST extend-project groups CDATA #IMPLIED>
<!ATTLIST extend-project revision CDATA #IMPLIED>
<!ATTLIST extend-project remote CDATA #IMPLIED>
<!ATTLIST extend-project dest-branch CDATA #IMPLIED>
<!ATTLIST extend-project upstream CDATA #IMPLIED>
<!ELEMENT remove-project EMPTY>
<!ATTLIST remove-project name CDATA #REQUIRED>
<!ATTLIST remove-project optional CDATA #IMPLIED>
<!ELEMENT repo-hooks EMPTY>
<!ATTLIST repo-hooks in-project CDATA #REQUIRED>
<!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
<!ELEMENT superproject EMPTY>
<!ATTLIST superproject name CDATA #REQUIRED>
<!ATTLIST superproject remote IDREF #IMPLIED>
<!ATTLIST superproject revision CDATA #IMPLIED>
<!ELEMENT contactinfo EMPTY>
<!ATTLIST contactinfo bugurl CDATA #REQUIRED>
<!ELEMENT include EMPTY>
<!ATTLIST include name CDATA #REQUIRED>
<!ATTLIST include groups CDATA #IMPLIED>
]>
```
For compatibility purposes across repo releases, all unknown elements are
silently ignored. However, repo reserves all possible names for itself for
future use. If you want to use custom elements, the `x-*` namespace is
reserved for that purpose, and repo guarantees to never allocate any
corresponding names.
A description of the elements and their attributes follows.
### Element manifest
The root element of the file.
### Element notice
Arbitrary text that is displayed to users whenever `repo sync` finishes.
The content is simply passed through as it exists in the manifest.
### Element remote
One or more remote elements may be specified. Each remote element
specifies a Git URL shared by one or more projects and (optionally)
the Gerrit review server those projects upload changes through.
Attribute `name`: A short name unique to this manifest file. The
name specified here is used as the remote name in each project's
.git/config, and is therefore automatically available to commands
like `git fetch`, `git remote`, `git pull` and `git push`.
Attribute `alias`: The alias, if specified, is used to override
`name` to be set as the remote name in each project's .git/config.
Its value can be duplicated while attribute `name` has to be unique
in the manifest file. This helps each project to be able to have
same remote name which actually points to different remote url.
Attribute `fetch`: The Git URL prefix for all projects which use
this remote. Each project's name is appended to this prefix to
form the actual URL used to clone the project.
Attribute `pushurl`: The Git "push" URL prefix for all projects
which use this remote. Each project's name is appended to this
prefix to form the actual URL used to "git push" the project.
This attribute is optional; if not specified then "git push"
will use the same URL as the `fetch` attribute.
Attribute `review`: Hostname of the Gerrit server where reviews
are uploaded to by `repo upload`. This attribute is optional;
if not specified then `repo upload` will not function.
Attribute `revision`: Name of a Git branch (e.g. `main` or
`refs/heads/main`). Remotes with their own revision will override
the default revision.
### Element default
At most one default element may be specified. Its remote and
revision attributes are used when a project element does not
specify its own remote or revision attribute.
Attribute `remote`: Name of a previously defined remote element.
Project elements lacking a remote attribute of their own will use
this remote.
Attribute `revision`: Name of a Git branch (e.g. `main` or
`refs/heads/main`). Project elements lacking their own
revision attribute will use this revision.
Attribute `dest-branch`: Name of a Git branch (e.g. `main`).
Project elements not setting their own `dest-branch` will inherit
this value. If this value is not set, projects will use `revision`
by default instead.
Attribute `upstream`: Name of the Git ref in which a sha1
can be found. Used when syncing a revision locked manifest in
-c mode to avoid having to sync the entire ref space. Project elements
not setting their own `upstream` will inherit this value.
Attribute `sync-j`: Number of parallel jobs to use when synching.
Attribute `sync-c`: Set to true to only sync the given Git
branch (specified in the `revision` attribute) rather than the
whole ref space. Project elements lacking a sync-c element of
their own will use this value.
Attribute `sync-s`: Set to true to also sync sub-projects.
Attribute `sync-tags`: Set to false to only sync the given Git
branch (specified in the `revision` attribute) rather than
the other ref tags.
### Element manifest-server
At most one manifest-server may be specified. The url attribute
is used to specify the URL of a manifest server, which is an
XML RPC service.
The manifest server should implement the following RPC methods:
GetApprovedManifest(branch, target)
Return a manifest in which each project is pegged to a known good revision
for the current branch and target. This is used by repo sync when the
--smart-sync option is given.
The target to use is defined by environment variables TARGET_PRODUCT
and TARGET_BUILD_VARIANT. These variables are used to create a string
of the form $TARGET_PRODUCT-$TARGET_BUILD_VARIANT, e.g. passion-userdebug.
If one of those variables or both are not present, the program will call
GetApprovedManifest without the target parameter and the manifest server
should choose a reasonable default target.
GetManifest(tag)
Return a manifest in which each project is pegged to the revision at
the specified tag. This is used by repo sync when the --smart-tag option
is given.
### Element submanifest
One or more submanifest elements may be specified. Each element describes a
single manifest to be checked out as a child.
Attribute `name`: A unique name (within the current (sub)manifest) for this
submanifest. It acts as a default for `revision` below. The same name can be
used for submanifests with different parent (sub)manifests.
Attribute `remote`: Name of a previously defined remote element.
If not supplied the remote given by the default element is used.
Attribute `project`: The manifest project name. The project's name is appended
onto its remote's fetch URL to generate the actual URL to configure the Git
remote with. The URL gets formed as:
${remote_fetch}/${project_name}.git
where ${remote_fetch} is the remote's fetch attribute and
${project_name} is the project's name attribute. The suffix ".git"
is always appended as repo assumes the upstream is a forest of
bare Git repositories. If the project has a parent element, its
name will be prefixed by the parent's.
The project name must match the name Gerrit knows, if Gerrit is
being used for code reviews.
`project` must not be empty, and may not be an absolute path or use "." or ".."
path components. It is always interpreted relative to the remote's fetch
settings, so if a different base path is needed, declare a different remote
with the new settings needed.
If not supplied the remote and project for this manifest will be used: `remote`
cannot be supplied.
Projects from a submanifest and its submanifests are added to the
submanifest::path:<path_prefix> group.
Attribute `manifest-name`: The manifest filename in the manifest project. If
not supplied, `default.xml` is used.
Attribute `revision`: Name of a Git branch (e.g. "main" or "refs/heads/main"),
tag (e.g. "refs/tags/stable"), or a commit hash. If not supplied, `name` is
used.
Attribute `path`: An optional path relative to the top directory
of the repo client where the submanifest repo client top directory
should be placed. If not supplied, `revision` is used.
`path` may not be an absolute path or use "." or ".." path components.
Attribute `groups`: List of additional groups to which all projects
in the included submanifest belong. This appends and recurses, meaning
all projects in submanifests carry all parent submanifest groups.
Same syntax as the corresponding element of `project`.
Attribute `default-groups`: The list of manifest groups to sync if no
`--groups=` parameter was specified at init. When that list is empty, use this
list instead of "default" as the list of groups to sync.
### Element project
One or more project elements may be specified. Each element
describes a single Git repository to be cloned into the repo
client workspace. You may specify Git-submodules by creating a
nested project. Git-submodules will be automatically
recognized and inherit their parent's attributes, but those
may be overridden by an explicitly specified project element.
Attribute `name`: A unique name for this project. The project's
name is appended onto its remote's fetch URL to generate the actual
URL to configure the Git remote with. The URL gets formed as:
${remote_fetch}/${project_name}.git
where ${remote_fetch} is the remote's fetch attribute and
${project_name} is the project's name attribute. The suffix ".git"
is always appended as repo assumes the upstream is a forest of
bare Git repositories. If the project has a parent element, its
name will be prefixed by the parent's.
The project name must match the name Gerrit knows, if Gerrit is
being used for code reviews.
"name" must not be empty, and may not be an absolute path or use "." or ".."
path components. It is always interpreted relative to the remote's fetch
settings, so if a different base path is needed, declare a different remote
with the new settings needed.
These restrictions are not enforced for [Local Manifests].
Attribute `path`: An optional path relative to the top directory
of the repo client where the Git working directory for this project
should be placed. If not supplied the project "name" is used.
If the project has a parent element, its path will be prefixed
by the parent's.
"path" may not be an absolute path or use "." or ".." path components.
These restrictions are not enforced for [Local Manifests].
If you want to place files into the root of the checkout (e.g. a README or
Makefile or another build script), use the [copyfile] or [linkfile] elements
instead.
Attribute `remote`: Name of a previously defined remote element.
If not supplied the remote given by the default element is used.
Attribute `revision`: Name of the Git branch the manifest wants
to track for this project. Names can be relative to refs/heads
(e.g. just "main") or absolute (e.g. "refs/heads/main").
Tags and/or explicit SHA-1s should work in theory, but have not
been extensively tested. If not supplied the revision given by
the remote element is used if applicable, else the default
element is used.
Attribute `dest-branch`: Name of a Git branch (e.g. `main`).
When using `repo upload`, changes will be submitted for code
review on this branch. If unspecified both here and in the
default element, `revision` is used instead.
Attribute `groups`: List of groups to which this project belongs,
whitespace or comma separated. All projects belong to the group
"all", and each project automatically belongs to a group of
its name:`name` and path:`path`. E.g. for
`<project name="monkeys" path="barrel-of"/>`, that project
definition is implicitly in the following manifest groups:
default, name:monkeys, and path:barrel-of. If you place a project in the
group "notdefault", it will not be automatically downloaded by repo.
If the project has a parent element, the `name` and `path` here
are the prefixed ones.
Attribute `sync-c`: Set to true to only sync the given Git
branch (specified in the `revision` attribute) rather than the
whole ref space.
Attribute `sync-s`: Set to true to also sync sub-projects.
Attribute `upstream`: Name of the Git ref in which a sha1
can be found. Used when syncing a revision locked manifest in
-c mode to avoid having to sync the entire ref space.
Attribute `clone-depth`: Set the depth to use when fetching this
project. If specified, this value will override any value given
to repo init with the --depth option on the command line.
Attribute `force-path`: Set to true to force this project to create the
local mirror repository according to its `path` attribute (if supplied)
rather than the `name` attribute. This attribute only applies to the
local mirrors syncing, it will be ignored when syncing the projects in a
client working directory.
### Element extend-project
Modify the attributes of the named project.
This element is mostly useful in a local manifest file, to modify the
attributes of an existing project without completely replacing the
existing project definition. This makes the local manifest more robust
against changes to the original manifest.
Attribute `path`: If specified, limit the change to projects checked out
at the specified path, rather than all projects with the given name.
Attribute `dest-path`: If specified, a path relative to the top directory
of the repo client where the Git working directory for this project
should be placed. This is used to move a project in the checkout by
overriding the existing `path` setting.
Attribute `groups`: List of additional groups to which this project
belongs. Same syntax as the corresponding element of `project`.
Attribute `revision`: If specified, overrides the revision of the original
project. Same syntax as the corresponding element of `project`.
Attribute `remote`: If specified, overrides the remote of the original
project. Same syntax as the corresponding element of `project`.
Attribute `dest-branch`: If specified, overrides the dest-branch of the original
project. Same syntax as the corresponding element of `project`.
Attribute `upstream`: If specified, overrides the upstream of the original
project. Same syntax as the corresponding element of `project`.
### Element annotation
Zero or more annotation elements may be specified as children of a
project or remote element. Each element describes a name-value pair.
For projects, this name-value pair will be exported into each project's
environment during a 'forall' command, prefixed with `REPO__`. In addition,
there is an optional attribute "keep" which accepts the case insensitive values
"true" (default) or "false". This attribute determines whether or not the
annotation will be kept when exported with the manifest subcommand.
### Element copyfile
Zero or more copyfile elements may be specified as children of a
project element. Each element describes a src-dest pair of files;
the "src" file will be copied to the "dest" place during `repo sync`
command.
"src" is project relative, "dest" is relative to the top of the tree.
Copying from paths outside of the project or to paths outside of the repo
client is not allowed.
"src" and "dest" must be files. Directories or symlinks are not allowed.
Intermediate paths must not be symlinks either.
Parent directories of "dest" will be automatically created if missing.
### Element linkfile
It's just like copyfile and runs at the same time as copyfile but
instead of copying it creates a symlink.
The symlink is created at "dest" (relative to the top of the tree) and
points to the path specified by "src" which is a path in the project.
Parent directories of "dest" will be automatically created if missing.
The symlink target may be a file or directory, but it may not point outside
of the repo client.
### Element remove-project
Deletes the named project from the internal manifest table, possibly
allowing a subsequent project element in the same manifest file to
replace the project with a different source.
This element is mostly useful in a local manifest file, where
the user can remove a project, and possibly replace it with their
own definition.
Attribute `optional`: Set to true to ignore remove-project elements with no
matching `project` element.
### Element repo-hooks
NB: See the [practical documentation](./repo-hooks.md) for using repo hooks.
Only one repo-hooks element may be specified at a time.
Attempting to redefine it will fail to parse.
Attribute `in-project`: The project where the hooks are defined. The value
must match the `name` attribute (**not** the `path` attribute) of a previously
defined `project` element.
Attribute `enabled-list`: List of hooks to use, whitespace or comma separated.
### Element superproject
***
*Note*: This is currently a WIP.
***
NB: See the [git superprojects documentation](
https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects) for background
information.
This element is used to specify the URL of the superproject. It has "name" and
"remote" as atrributes. Only "name" is required while the others have
reasonable defaults. At most one superproject may be specified.
Attempting to redefine it will fail to parse.
Attribute `name`: A unique name for the superproject. This attribute has the
same meaning as project's name attribute. See the
[element project](#element-project) for more information.
Attribute `remote`: Name of a previously defined remote element.
If not supplied the remote given by the default element is used.
Attribute `revision`: Name of the Git branch the manifest wants
to track for this superproject. If not supplied the revision given
by the remote element is used if applicable, else the default
element is used.
### Element contactinfo
***
*Note*: This is currently a WIP.
***
This element is used to let manifest authors self-register contact info.
It has "bugurl" as a required atrribute. This element can be repeated,
and any later entries will clobber earlier ones. This would allow manifest
authors who extend manifests to specify their own contact info.
Attribute `bugurl`: The URL to file a bug against the manifest owner.
### Element include
This element provides the capability of including another manifest
file into the originating manifest. Normal rules apply for the
target manifest to include - it must be a usable manifest on its own.
Attribute `name`: the manifest to include, specified relative to
the manifest repository's root.
"name" may not be an absolute path or use "." or ".." path components.
These restrictions are not enforced for [Local Manifests].
Attribute `groups`: List of additional groups to which all projects
in the included manifest belong. This appends and recurses, meaning
all projects in included manifests carry all parent include groups.
Same syntax as the corresponding element of `project`.
## Local Manifests {#local-manifests}
Additional remotes and projects may be added through local manifest
files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
For example:
$ ls .repo/local_manifests
local_manifest.xml
another_local_manifest.xml
$ cat .repo/local_manifests/local_manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<project path="manifest"
name="tools/manifest" />
<project path="platform-manifest"
name="platform/manifest" />
</manifest>
Users may add projects to the local manifest(s) prior to a `repo sync`
invocation, instructing repo to automatically download and manage
these extra projects.
Manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml` will
be loaded in alphabetical order.
Projects from local manifest files are added into
local::<local manifest filename> group.
The legacy `$TOP_DIR/.repo/local_manifest.xml` path is no longer supported.
[copyfile]: #Element-copyfile
[linkfile]: #Element-linkfile
[Local Manifests]: #local-manifests

View File

@ -1,316 +0,0 @@
repo Manifest Format
====================
A repo manifest describes the structure of a repo client; that is
the directories that are visible and where they should be obtained
from with git.
The basic structure of a manifest is a bare Git repository holding
a single 'default.xml' XML file in the top level directory.
Manifests are inherently version controlled, since they are kept
within a Git repository. Updates to manifests are automatically
obtained by clients during `repo sync`.
XML File Format
---------------
A manifest XML file (e.g. 'default.xml') roughly conforms to the
following DTD:
<!DOCTYPE manifest [
<!ELEMENT manifest (notice?,
remote*,
default?,
manifest-server?,
remove-project*,
project*,
repo-hooks?)>
<!ELEMENT notice (#PCDATA)>
<!ELEMENT remote (EMPTY)>
<!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED>
<!ATTLIST remote review CDATA #IMPLIED>
<!ELEMENT default (EMPTY)>
<!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED>
<!ATTLIST default dest-branch CDATA #IMPLIED>
<!ATTLIST default sync-j CDATA #IMPLIED>
<!ATTLIST default sync-c CDATA #IMPLIED>
<!ATTLIST default sync-s CDATA #IMPLIED>
<!ELEMENT manifest-server (EMPTY)>
<!ATTLIST url CDATA #REQUIRED>
<!ELEMENT project (annotation*,
project*)>
<!ATTLIST project name CDATA #REQUIRED>
<!ATTLIST project path CDATA #IMPLIED>
<!ATTLIST project remote IDREF #IMPLIED>
<!ATTLIST project revision CDATA #IMPLIED>
<!ATTLIST project dest-branch CDATA #IMPLIED>
<!ATTLIST project groups CDATA #IMPLIED>
<!ATTLIST project sync-c CDATA #IMPLIED>
<!ATTLIST project sync-s CDATA #IMPLIED>
<!ATTLIST project upstream CDATA #IMPLIED>
<!ATTLIST project clone-depth CDATA #IMPLIED>
<!ATTLIST project force-path CDATA #IMPLIED>
<!ELEMENT annotation (EMPTY)>
<!ATTLIST annotation name CDATA #REQUIRED>
<!ATTLIST annotation value CDATA #REQUIRED>
<!ATTLIST annotation keep CDATA "true">
<!ELEMENT remove-project (EMPTY)>
<!ATTLIST remove-project name CDATA #REQUIRED>
<!ELEMENT repo-hooks (EMPTY)>
<!ATTLIST repo-hooks in-project CDATA #REQUIRED>
<!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
<!ELEMENT include (EMPTY)>
<!ATTLIST include name CDATA #REQUIRED>
]>
A description of the elements and their attributes follows.
Element manifest
----------------
The root element of the file.
Element remote
--------------
One or more remote elements may be specified. Each remote element
specifies a Git URL shared by one or more projects and (optionally)
the Gerrit review server those projects upload changes through.
Attribute `name`: A short name unique to this manifest file. The
name specified here is used as the remote name in each project's
.git/config, and is therefore automatically available to commands
like `git fetch`, `git remote`, `git pull` and `git push`.
Attribute `alias`: The alias, if specified, is used to override
`name` to be set as the remote name in each project's .git/config.
Its value can be duplicated while attribute `name` has to be unique
in the manifest file. This helps each project to be able to have
same remote name which actually points to different remote url.
Attribute `fetch`: The Git URL prefix for all projects which use
this remote. Each project's name is appended to this prefix to
form the actual URL used to clone the project.
Attribute `review`: Hostname of the Gerrit server where reviews
are uploaded to by `repo upload`. This attribute is optional;
if not specified then `repo upload` will not function.
Element default
---------------
At most one default element may be specified. Its remote and
revision attributes are used when a project element does not
specify its own remote or revision attribute.
Attribute `remote`: Name of a previously defined remote element.
Project elements lacking a remote attribute of their own will use
this remote.
Attribute `revision`: Name of a Git branch (e.g. `master` or
`refs/heads/master`). Project elements lacking their own
revision attribute will use this revision.
Attribute `dest-branch`: Name of a Git branch (e.g. `master`).
Project elements not setting their own `dest-branch` will inherit
this value. If this value is not set, projects will use `revision`
by default instead.
Attribute `sync_j`: Number of parallel jobs to use when synching.
Attribute `sync_c`: Set to true to only sync the given Git
branch (specified in the `revision` attribute) rather than the
whole ref space. Project elements lacking a sync_c element of
their own will use this value.
Attribute `sync_s`: Set to true to also sync sub-projects.
Element manifest-server
-----------------------
At most one manifest-server may be specified. The url attribute
is used to specify the URL of a manifest server, which is an
XML RPC service.
The manifest server should implement the following RPC methods:
GetApprovedManifest(branch, target)
Return a manifest in which each project is pegged to a known good revision
for the current branch and target.
The target to use is defined by environment variables TARGET_PRODUCT
and TARGET_BUILD_VARIANT. These variables are used to create a string
of the form $TARGET_PRODUCT-$TARGET_BUILD_VARIANT, e.g. passion-userdebug.
If one of those variables or both are not present, the program will call
GetApprovedManifest without the target parameter and the manifest server
should choose a reasonable default target.
GetManifest(tag)
Return a manifest in which each project is pegged to the revision at
the specified tag.
Element project
---------------
One or more project elements may be specified. Each element
describes a single Git repository to be cloned into the repo
client workspace. You may specify Git-submodules by creating a
nested project. Git-submodules will be automatically
recognized and inherit their parent's attributes, but those
may be overridden by an explicitly specified project element.
Attribute `name`: A unique name for this project. The project's
name is appended onto its remote's fetch URL to generate the actual
URL to configure the Git remote with. The URL gets formed as:
${remote_fetch}/${project_name}.git
where ${remote_fetch} is the remote's fetch attribute and
${project_name} is the project's name attribute. The suffix ".git"
is always appended as repo assumes the upstream is a forest of
bare Git repositories. If the project has a parent element, its
name will be prefixed by the parent's.
The project name must match the name Gerrit knows, if Gerrit is
being used for code reviews.
Attribute `path`: An optional path relative to the top directory
of the repo client where the Git working directory for this project
should be placed. If not supplied the project name is used.
If the project has a parent element, its path will be prefixed
by the parent's.
Attribute `remote`: Name of a previously defined remote element.
If not supplied the remote given by the default element is used.
Attribute `revision`: Name of the Git branch the manifest wants
to track for this project. Names can be relative to refs/heads
(e.g. just "master") or absolute (e.g. "refs/heads/master").
Tags and/or explicit SHA-1s should work in theory, but have not
been extensively tested. If not supplied the revision given by
the default element is used.
Attribute `dest-branch`: Name of a Git branch (e.g. `master`).
When using `repo upload`, changes will be submitted for code
review on this branch. If unspecified both here and in the
default element, `revision` is used instead.
Attribute `groups`: List of groups to which this project belongs,
whitespace or comma separated. All projects belong to the group
"all", and each project automatically belongs to a group of
its name:`name` and path:`path`. E.g. for
<project name="monkeys" path="barrel-of"/>, that project
definition is implicitly in the following manifest groups:
default, name:monkeys, and path:barrel-of. If you place a project in the
group "notdefault", it will not be automatically downloaded by repo.
If the project has a parent element, the `name` and `path` here
are the prefixed ones.
Attribute `sync_c`: Set to true to only sync the given Git
branch (specified in the `revision` attribute) rather than the
whole ref space.
Attribute `sync_s`: Set to true to also sync sub-projects.
Attribute `upstream`: Name of the Git branch in which a sha1
can be found. Used when syncing a revision locked manifest in
-c mode to avoid having to sync the entire ref space.
Attribute `clone-depth`: Set the depth to use when fetching this
project. If specified, this value will override any value given
to repo init with the --depth option on the command line.
Attribute `force-path`: Set to true to force this project to create the
local mirror repository according to its `path` attribute (if supplied)
rather than the `name` attribute. This attribute only applies to the
local mirrors syncing, it will be ignored when syncing the projects in a
client working directory.
Element annotation
------------------
Zero or more annotation elements may be specified as children of a
project element. Each element describes a name-value pair that will be
exported into each project's environment during a 'forall' command,
prefixed with REPO__. In addition, there is an optional attribute
"keep" which accepts the case insensitive values "true" (default) or
"false". This attribute determines whether or not the annotation will
be kept when exported with the manifest subcommand.
Element remove-project
----------------------
Deletes the named project from the internal manifest table, possibly
allowing a subsequent project element in the same manifest file to
replace the project with a different source.
This element is mostly useful in a local manifest file, where
the user can remove a project, and possibly replace it with their
own definition.
Element include
---------------
This element provides the capability of including another manifest
file into the originating manifest. Normal rules apply for the
target manifest to include - it must be a usable manifest on its own.
Attribute `name`: the manifest to include, specified relative to
the manifest repository's root.
Local Manifests
===============
Additional remotes and projects may be added through local manifest
files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.
For example:
$ ls .repo/local_manifests
local_manifest.xml
another_local_manifest.xml
$ cat .repo/local_manifests/local_manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<project path="manifest"
name="tools/manifest" />
<project path="platform-manifest"
name="platform/manifest" />
</manifest>
Users may add projects to the local manifest(s) prior to a `repo sync`
invocation, instructing repo to automatically download and manage
these extra projects.
Manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml` will
be loaded in alphabetical order.
Additional remotes and projects may also be added through a local
manifest, stored in `$TOP_DIR/.repo/local_manifest.xml`. This method
is deprecated in favor of using multiple manifest files as mentioned
above.
If `$TOP_DIR/.repo/local_manifest.xml` exists, it will be loaded before
any manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml`.

47
docs/python-support.md Normal file
View File

@ -0,0 +1,47 @@
# Supported Python Versions
With Python 2.7 officially going EOL on [01 Jan 2020](https://pythonclock.org/),
we need a support plan for the repo project itself.
Inevitably, there will be a long tail of users who still want to use Python 2 on
their old LTS/corp systems and have little power to change the system.
## Summary
* Python 3.6 (released Dec 2016) is required by default starting with repo-2.x.
* Older versions of Python (e.g. v2.7) may use the legacy feature-frozen branch
based on repo-1.x.
## Overview
We provide a branch for Python 2 users that is feature-frozen.
Bugfixes may be added on a best-effort basis or from the community, but largely
no new features will be added, nor is support guaranteed.
Users can select this during `repo init` time via the [repo launcher].
Otherwise the default branches (e.g. stable & main) will be used which will
require Python 3.
This means the [repo launcher] needs to support both Python 2 & Python 3, but
since it doesn't import any other repo code, this shouldn't be too problematic.
The main branch will require Python 3.6 at a minimum.
If the system has an older version of Python 3, then users will have to select
the legacy Python 2 branch instead.
### repo hooks
Projects that use [repo hooks] run on independent schedules.
They might migrate to Python 3 earlier or later than us.
To support them, we'll probe the shebang of the hook script and if we find an
interpreter in there that indicates a different version than repo is currently
running under, we'll attempt to reexec ourselves under that.
For example, a hook with a header like `#!/usr/bin/python2` will have repo
execute `/usr/bin/python2` to execute the hook code specifically if repo is
currently running Python 3.
For more details, consult the [repo hooks] documentation.
[repo hooks]: ./repo-hooks.md
[repo launcher]: ../repo

341
docs/release-process.md Normal file
View File

@ -0,0 +1,341 @@
# repo release process
This is the process for creating a new release of repo, as well as all the
related topics and flows.
[TOC]
## Schedule
There is no specific schedule for when releases are made.
Usually it's more along the lines of "enough minor changes have been merged",
or "there's a known issue the maintainers know should get fixed".
If you find a fix has been merged for an issue important to you, but hasn't been
released after a week or so, feel free to [contact] us to request a new release.
### Release Freezes {#freeze}
We try to observe a regular schedule for when **not** to release.
If something goes wrong, staff need to be active in order to respond quickly &
effectively.
We also don't want to disrupt non-Google organizations if possible.
We generally follow the rules:
* Release during Mon - Thu, 9:00 - 14:00 [US PT]
* Avoid holidays
* All regular [US holidays]
* Large international ones if possible
* All the various [New Years]
* Jan 1 in Gregorian calendar is the most obvious
* Check for large Lunar New Years too
* Follow the normal [Google production freeze schedule]
[US holidays]: https://en.wikipedia.org/wiki/Federal_holidays_in_the_United_States
[US PT]: https://en.wikipedia.org/wiki/Pacific_Time_Zone
[New Years]: https://en.wikipedia.org/wiki/New_Year
[Google production freeze schedule]: http://goto.google.com/prod-freeze
## Launcher script
The main repo script serves as a standalone program and is often referred to as
the "launcher script".
This makes it easy to copy around and install as you don't have to install any
other files from the git repo.
Whenever major changes are made to the launcher script, you should increment the
`VERSION` variable in the launcher itself.
At runtime, repo will check this to see if it needs to be updated (and notify
the user automatically).
## Key management
Every release has a git tag that is signed with a key that repo recognizes.
Those keys are hardcoded inside of the repo launcher itself -- look for the
`KEYRING_VERSION` and `MAINTAINER_KEYS` settings.
Adding new keys to the repo launcher will allow tags to be recognized by new
keys, but only people using that updated version will be able to.
Since the majority of users will be using an official launcher version, their
version will simply ignore any new signed tags.
If you want to add new keys, it's best to register them long ahead of time,
and then wait for that updated launcher to make its way out to everyone.
Even then, there will be a long tail of users with outdated launchers, so be
prepared for people asking questions.
### Registering a new key
The process of actually adding a new key is quite simple.
1. Add the public half of the key to `MAINTAINER_KEYS`.
2. Increment `KEYRING_VERSION` so repo knows it needs to update.
3. Wait a long time after that version is in a release (~months) before trying
to create a new release using those new keys.
## Self update algorithm
When creating a new repo checkout with `repo init`, there are a few options that
control how repo finds updates:
* `--repo-url`: This tells repo where to clone the full repo project itself.
It defaults to the official project (`REPO_URL` in the launcher script).
* `--repo-rev`: This tells repo which branch to use for the full project.
It defaults to the `stable` branch (`REPO_REV` in the launcher script).
Whenever `repo sync` is run, repo will, once every 24 hours, see if an update
is available.
It fetches the latest repo-rev from the repo-url.
Then it verifies that the latest commit in the branch has a valid signed tag
using `git tag -v` (which uses gpg).
If the tag is valid, then repo will update its internal checkout to it.
If the latest commit doesn't have a signed tag, repo will fall back to the
most recent tag it can find (via `git describe`).
If that tag is valid, then repo will warn and use that commit instead.
If that tag cannot be verified, it gives up and forces the user to resolve.
### Force an update
The `repo selfupdate` command can be used to force an immediate update.
It is not subject to the 24 hour limitation.
## Branch management
All development happens on the `main` branch and should generally be stable.
Since the repo launcher defaults to tracking the `stable` branch, it is not
normally updated until a new release is available.
If something goes wrong with a new release, an older release can be force pushed
and clients will automatically downgrade.
The `maint` branch is used to track the previous major release of repo.
It is not normally meant to be used by people as `stable` should be good enough.
Once a new major release is pushed to the `stable` branch, then the previous
major release can be pushed to `maint`.
For example, when `stable` moves from `v1.10.x` to `v1.11.x`, then the `maint`
branch will be updated from `v1.9.x` to `v1.10.x`.
We don't have parallel release branches/series.
Typically all tags are made against the `main` branch and then pushed to the
`stable` branch to make it available to the rest of the world.
Since repo doesn't typically see a lot of changes, this tends to be OK.
## Creating a new release
When you want to create a new release, you'll need to select a good version and
create a signed tag using a key registered in repo itself.
Typically we just tag the latest version of the `main` branch.
The tag could be pushed now, but it won't be used by clients normally (since the
default `repo-rev` setting is `stable`).
This would allow some early testing on systems who explicitly select `main`.
### Creating a signed tag
Lets assume your keys live in a dedicated directory, e.g. `~/.gnupg/repo/`.
*** note
If you need access to the official keys, check out the internal documentation
at [go/repo-release].
Note that only official maintainers of repo will have access as it describes
internal processes for accessing the restricted keys.
***
```sh
# Set the gpg key directory.
$ export GNUPGHOME=~/.gnupg/repo/
# Verify the listed key is “Repo Maintainer”.
$ gpg -K
# Pick whatever branch or commit you want to tag.
$ r=main
# Pick the new version.
$ t=1.12.10
# Create the signed tag.
$ git tag -s v$t -u "Repo Maintainer <repo@android.kernel.org>" -m "repo $t" $r
# Verify the signed tag.
$ git show v$t
```
### Push the new release
Once you're ready to make the release available to everyone, push it to the
`stable` branch.
Make sure you never push the tag itself to the stable branch!
Only push the commit -- notice the use of `$t` and `$r` below.
```sh
$ git push https://gerrit-review.googlesource.com/git-repo v$t
$ git push https://gerrit-review.googlesource.com/git-repo $r:stable
```
If something goes horribly wrong, you can force push the previous version to the
`stable` branch and people should automatically recover.
Again, make sure you never push the tag itself!
```sh
$ oldrev="whatever-old-commit"
$ git push https://gerrit-review.googlesource.com/git-repo $oldrev:stable --force
```
### Announce the release
Once you do push a new release to `stable`, make sure to announce it on the
[repo-discuss@googlegroups.com] group.
Here is an [example announcement].
You can create a short changelog using the command:
```sh
# If you haven't pushed to the stable branch yet, you can use origin/stable.
# If you have pushed, change origin/stable to the previous release tag.
$ git log --format="%h (%aN) %s" --no-merges origin/stable..$r
```
## Project References
Here's a table showing the relationship of major tools, their EOL dates, and
their status in Ubuntu & Debian.
Those distros tend to be good indicators of how long we need to support things.
Things in bold indicate stuff to take note of, but does not guarantee that we
still support them.
Things in italics are things we used to care about but probably don't anymore.
| Date | EOL | [Git][rel-g] | [Python][rel-p] | [SSH][rel-o] | [Ubuntu][rel-u] / [Debian][rel-d] | Git | Python | SSH |
|:--------:|:------------:|:------------:|:---------------:|:------------:|-----------------------------------|-----|--------|-----|
| Apr 2008 | | | | 5.0 |
| Jun 2008 | | | | 5.1 |
| Oct 2008 | *Oct 2013* | | 2.6.0 | | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
| Dec 2008 | *Feb 2009* | | 3.0.0 |
| Feb 2009 | | | | 5.2 |
| Feb 2009 | *Mar 2012* | | | | Debian 5 Lenny | 1.5.6.5 | 2.5.2 |
| Jun 2009 | *Jun 2016* | | 3.1.0 | | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
| Sep 2009 | | | | 5.3 | *10.04 Lucid* |
| Feb 2010 | *Oct 2012* | 1.7.0 | | | *10.04 Lucid* - *12.04 Precise* - 12.10 Quantal |
| Mar 2010 | | | | 5.4 |
| Apr 2010 | | | | 5.5 | 10.10 Maverick |
| Apr 2010 | *Apr 2015* | | | | *10.04 Lucid* | 1.7.0.4 | 2.6.5 3.1.2 | 5.3 |
| Jul 2010 | *Dec 2019* | | *2.7.0* | | 11.04 Natty - *<current>* |
| Aug 2010 | | | | 5.6 |
| Oct 2010 | | | | | 10.10 Maverick | 1.7.1 | 2.6.6 3.1.3 | 5.5 |
| Jan 2011 | | | | 5.7 |
| Feb 2011 | | | | 5.8 | 11.04 Natty |
| Feb 2011 | *Feb 2016* | | | | Debian 6 Squeeze | 1.7.2.5 | 2.6.6 3.1.3 |
| Apr 2011 | | | | | 11.04 Natty | 1.7.4 | 2.7.1 3.2.0 | 5.8 |
| Sep 2011 | | | | 5.9 | *12.04 Precise* |
| Oct 2011 | *Feb 2016* | | 3.2.0 | | 11.04 Natty - 12.10 Quantal |
| Oct 2011 | | | | | 11.10 Ocelot | 1.7.5.4 | 2.7.2 3.2.2 | 5.8 |
| Apr 2012 | | | | 6.0 | 12.10 Quantal |
| Apr 2012 | *Apr 2019* | | | | *12.04 Precise* | 1.7.9.5 | 2.7.3 3.2.3 | 5.9 |
| Aug 2012 | | | | 6.1 | 13.04 Raring |
| Sep 2012 | *Sep 2017* | | 3.3.0 | | 13.04 Raring - 13.10 Saucy |
| Oct 2012 | *Dec 2014* | 1.8.0 | | | 13.04 Raring - 13.10 Saucy |
| Oct 2012 | | | | | 12.10 Quantal | 1.7.10.4 | 2.7.3 3.2.3 | 6.0 |
| Mar 2013 | | | | 6.2 | 13.10 Saucy |
| Apr 2013 | | | | | 13.04 Raring | 1.8.1.2 | 2.7.4 3.3.1 | 6.1 |
| May 2013 | *May 2018* | | | | Debian 7 Wheezy | 1.7.10.4 | 2.7.3 3.2.3 |
| Sep 2013 | | | | 6.3 |
| Oct 2013 | | | | | 13.10 Saucy | 1.8.3.2 | 2.7.5 3.3.2 | 6.2 |
| Nov 2013 | | | | 6.4 |
| Jan 2014 | | | | 6.5 |
| Feb 2014 | *Dec 2014* | **1.9.0** | | | *14.04 Trusty* |
| Mar 2014 | *Mar 2019* | | *3.4.0* | | *14.04 Trusty* - 15.10 Wily / *Jessie* |
| Mar 2014 | | | | 6.6 | *14.04 Trusty* - 14.10 Utopic |
| Apr 2014 | *Apr 2022* | | | | *14.04 Trusty* | 1.9.1 | 2.7.5 3.4.0 | 6.6 |
| May 2014 | *Dec 2014* | 2.0.0 |
| Aug 2014 | *Dec 2014* | *2.1.0* | | | 14.10 Utopic - 15.04 Vivid / *Jessie* |
| Oct 2014 | | | | 6.7 | 15.04 Vivid |
| Oct 2014 | | | | | 14.10 Utopic | 2.1.0 | 2.7.8 3.4.2 | 6.6 |
| Nov 2014 | *Sep 2015* | 2.2.0 |
| Feb 2015 | *Sep 2015* | 2.3.0 |
| Mar 2015 | | | | 6.8 |
| Apr 2015 | *May 2017* | 2.4.0 |
| Apr 2015 | *Jun 2020* | | | | *Debian 8 Jessie* | 2.1.4 | 2.7.9 3.4.2 |
| Apr 2015 | | | | | 15.04 Vivid | 2.1.4 | 2.7.9 3.4.3 | 6.7 |
| Jul 2015 | *May 2017* | 2.5.0 | | | 15.10 Wily |
| Jul 2015 | | | | 6.9 | 15.10 Wily |
| Aug 2015 | | | | 7.0 |
| Aug 2015 | | | | 7.1 |
| Sep 2015 | *May 2017* | 2.6.0 |
| Sep 2015 | *Sep 2020* | | *3.5.0* | | *16.04 Xenial* - 17.04 Zesty / *Stretch* |
| Oct 2015 | | | | | 15.10 Wily | 2.5.0 | 2.7.9 3.4.3 | 6.9 |
| Jan 2016 | *Jul 2017* | *2.7.0* | | | *16.04 Xenial* |
| Feb 2016 | | | | 7.2 | *16.04 Xenial* |
| Mar 2016 | *Jul 2017* | 2.8.0 |
| Apr 2016 | *Apr 2024* | | | | *16.04 Xenial* | 2.7.4 | 2.7.11 3.5.1 | 7.2 |
| Jun 2016 | *Jul 2017* | 2.9.0 | | | 16.10 Yakkety |
| Jul 2016 | | | | 7.3 | 16.10 Yakkety |
| Sep 2016 | *Sep 2017* | 2.10.0 |
| Oct 2016 | | | | | 16.10 Yakkety | 2.9.3 | 2.7.11 3.5.1 | 7.3 |
| Nov 2016 | *Sep 2017* | *2.11.0* | | | 17.04 Zesty / *Stretch* |
| Dec 2016 | **Dec 2021** | | **3.6.0** | | 17.10 Artful - **18.04 Bionic** - 18.10 Cosmic |
| Dec 2016 | | | | 7.4 | 17.04 Zesty / *Debian 9 Stretch* |
| Feb 2017 | *Sep 2017* | 2.12.0 |
| Mar 2017 | | | | 7.5 | 17.10 Artful |
| Apr 2017 | | | | | 17.04 Zesty | 2.11.0 | 2.7.13 3.5.3 | 7.4 |
| May 2017 | *May 2018* | 2.13.0 |
| Jun 2017 | *Jun 2022* | | | | *Debian 9 Stretch* | 2.11.0 | 2.7.13 3.5.3 | 7.4 |
| Aug 2017 | *Dec 2019* | 2.14.0 | | | 17.10 Artful |
| Oct 2017 | *Dec 2019* | 2.15.0 |
| Oct 2017 | | | | 7.6 | **18.04 Bionic** |
| Oct 2017 | | | | | 17.10 Artful | 2.14.1 | 2.7.14 3.6.3 | 7.5 |
| Jan 2018 | *Dec 2019* | 2.16.0 |
| Apr 2018 | *Mar 2021* | **2.17.0** | | | **18.04 Bionic** |
| Apr 2018 | | | | 7.7 | 18.10 Cosmic |
| Apr 2018 | **Apr 2028** | | | | **18.04 Bionic** | 2.17.0 | 2.7.15 3.6.5 | 7.6 |
| Jun 2018 | *Mar 2021* | 2.18.0 |
| Jun 2018 | **Jun 2023** | | 3.7.0 | | 19.04 Disco - **20.04 Focal** / **Buster** |
| Aug 2018 | | | | 7.8 |
| Sep 2018 | *Mar 2021* | 2.19.0 | | | 18.10 Cosmic |
| Oct 2018 | | | | 7.9 | 19.04 Disco / **Buster** |
| Oct 2018 | | | | | 18.10 Cosmic | 2.19.1 | 2.7.15 3.6.6 | 7.7 |
| Dec 2018 | *Mar 2021* | **2.20.0** | | | 19.04 Disco - 19.10 Eoan / **Buster** |
| Feb 2019 | *Mar 2021* | 2.21.0 |
| Apr 2019 | | | | 8.0 | 19.10 Eoan |
| Apr 2019 | | | | | 19.04 Disco | 2.20.1 | 2.7.16 3.7.3 | 7.9 |
| Jun 2019 | | 2.22.0 |
| Jul 2019 | **Jul 2024** | | | | **Debian 10 Buster** | 2.20.1 | 2.7.16 3.7.3 | 7.9 |
| Aug 2019 | *Mar 2021* | 2.23.0 |
| Oct 2019 | **Oct 2024** | | 3.8.0 | | **20.04 Focal** - 20.10 Groovy |
| Oct 2019 | | | | 8.1 |
| Oct 2019 | | | | | 19.10 Eoan | 2.20.1 | 2.7.17 3.7.5 | 8.0 |
| Nov 2019 | *Mar 2021* | 2.24.0 |
| Jan 2020 | *Mar 2021* | 2.25.0 | | | **20.04 Focal** |
| Feb 2020 | | | | 8.2 | **20.04 Focal** |
| Mar 2020 | *Mar 2021* | 2.26.0 |
| Apr 2020 | **Apr 2030** | | | | **20.04 Focal** | 2.25.1 | 2.7.17 3.8.2 | 8.2 |
| May 2020 | *Mar 2021* | 2.27.0 | | | 20.10 Groovy |
| May 2020 | | | | 8.3 |
| Jul 2020 | *Mar 2021* | 2.28.0 |
| Sep 2020 | | | | 8.4 | 21.04 Hirsute / **Bullseye** |
| Oct 2020 | *Mar 2021* | 2.29.0 |
| Oct 2020 | | | | | 20.10 Groovy | 2.27.0 | 2.7.18 3.8.6 | 8.3 |
| Oct 2020 | **Oct 2025** | | 3.9.0 | | 21.04 Hirsute / **Bullseye** |
| Dec 2020 | *Mar 2021* | 2.30.0 | | | 21.04 Hirsute / **Bullseye** |
| Mar 2021 | | 2.31.0 |
| Mar 2021 | | | | 8.5 |
| Apr 2021 | | | | 8.6 |
| Apr 2021 | *Jan 2022* | | | | 21.04 Hirsute | 2.30.2 | 2.7.18 3.9.4 | 8.4 |
| Jun 2021 | | 2.32.0 |
| Aug 2021 | | 2.33.0 |
| Aug 2021 | | | | 8.7 |
| Aug 2021 | **Aug 2026** | | | | **Debian 11 Bullseye** | 2.30.2 | 2.7.18 3.9.2 | 8.4 |
| **Date** | **EOL** | **[Git][rel-g]** | **[Python][rel-p]** | **[SSH][rel-o]** | **[Ubuntu][rel-u] / [Debian][rel-d]** | **Git** | **Python** | **SSH** |
[contact]: ../README.md#contact
[rel-d]: https://en.wikipedia.org/wiki/Debian_version_history
[rel-g]: https://en.wikipedia.org/wiki/Git#Releases
[rel-o]: https://www.openssh.com/releasenotes.html
[rel-p]: https://en.wikipedia.org/wiki/History_of_Python#Table_of_versions
[rel-u]: https://en.wikipedia.org/wiki/Ubuntu_version_history#Table_of_versions
[example announcement]: https://groups.google.com/d/topic/repo-discuss/UGBNismWo1M/discussion
[repo-discuss@googlegroups.com]: https://groups.google.com/forum/#!forum/repo-discuss
[go/repo-release]: https://goto.google.com/repo-release

135
docs/repo-hooks.md Normal file
View File

@ -0,0 +1,135 @@
# repo hooks
[TOC]
Repo provides a mechanism to hook specific stages of the runtime with custom
python modules. All the hooks live in one git project which is checked out by
the manifest (specified during `repo init`), and the manifest itself defines
which hooks are registered.
These are useful to run linters, check formatting, and run quick unittests
before allowing a step to proceed (e.g. before uploading a commit to Gerrit).
A complete example can be found in the Android project. It can be easily
re-used by any repo based project and is not specific to Android.<br>
https://android.googlesource.com/platform/tools/repohooks
## Approvals
When a hook is processed the first time, the user is prompted for approval.
We don't want to execute arbitrary code without explicit consent. For manifests
fetched via secure protocols (e.g. https://), the user is prompted once. For
insecure protocols (e.g. http://), the user is prompted whenever the registered
repohooks project is updated and a hook is triggered.
## Manifest Settings
For the full syntax, see the [repo manifest format](./manifest-format.md).
Here's a short example from
[Android](https://android.googlesource.com/platform/manifest/+/HEAD/default.xml).
The `<project>` line checks out the repohooks git repo to the local
`tools/repohooks/` path. The `<repo-hooks>` line says to look in the project
with the name `platform/tools/repohooks` for hooks to run during the
`pre-upload` phase.
```xml
<project path="tools/repohooks" name="platform/tools/repohooks" />
<repo-hooks in-project="platform/tools/repohooks" enabled-list="pre-upload" />
```
## Source Layout
The repohooks git repo should have a python file with the same name as the hook.
So if you want to support the `pre-upload` hook, you'll need to create a file
named `pre-upload.py`. Repo will dynamically load that module when processing
the hook and then call the `main` function in it.
Hooks should have their `main` accept `**kwargs` for future compatibility.
## Runtime
Hook return values are ignored.
Any uncaught exceptions from the hook will cause the step to fail. This is
intended as a fallback safety check though rather than the normal flow. If
you want your hook to trigger a failure, it should call `sys.exit()` (after
displaying relevant diagnostics).
Output (stdout & stderr) are not filtered in any way. Hooks should generally
not be too verbose. A short summary is nice, and some status information when
long running operations occur, but long/verbose output should be used only if
the hook ultimately fails.
The hook runs from the top level of the repo client where the operation is
started.
For example, if the repo client is under `~/tree/`, then that is where the hook
runs, even if you ran repo in a git repository at `~/tree/src/foo/`, or in a
subdirectory of that git repository in `~/tree/src/foo/bar/`.
Hooks frequently start off by doing a `os.chdir` to the specific project they're
called on (see below) and then changing back to the original dir when they're
finished.
Python's `sys.path` is modified so that the top of repohooks directory comes
first. This should help simplify the hook logic to easily allow importing of
local modules.
Repo does not modify the state of the git checkout. This means that the hooks
might be running in a dirty git repo with many commits and checked out to the
latest one. If the hook wants to operate on specific git commits, it needs to
manually discover the list of pending commits, extract the diff/commit, and
then check it directly. Hooks should not normally modify the active git repo
(such as checking out a specific commit to run checks) without first prompting
the user. Although user interaction is discouraged in the common case, it can
be useful when deploying automatic fixes.
### Shebang Handling
*** note
This is intended as a transitional feature. Hooks are expected to eventually
migrate to Python 3 only as Python 2 is EOL & deprecated.
***
If the hook is written against a specific version of Python (either 2 or 3),
the script can declare that explicitly. Repo will then attempt to execute it
under the right version of Python regardless of the version repo itself might
be executing under.
Here are the shebangs that are recognized.
* `#!/usr/bin/env python` & `#!/usr/bin/python`: The hook is compatible with
Python 2 & Python 3. For maximum compatibility, these are recommended.
* `#!/usr/bin/env python2` & `#!/usr/bin/python2`: The hook requires Python 2.
Version specific names like `python2.7` are also recognized.
* `#!/usr/bin/env python3` & `#!/usr/bin/python3`: The hook requires Python 3.
Version specific names like `python3.6` are also recognized.
If no shebang is detected, or does not match the forms above, we assume that the
hook is compatible with both Python 2 & Python 3 as if `#!/usr/bin/python` was
used.
## Hooks
Here are all the points available for hooking.
### pre-upload
This hook runs when people run `repo upload`.
The `pre-upload.py` file should be defined like:
```py
def main(project_list, worktree_list=None, **kwargs):
"""Main function invoked directly by repo.
We must use the name "main" as that is what repo requires.
Args:
project_list: List of projects to run on.
worktree_list: A list of directories. It should be the same length as
project_list, so that each entry in project_list matches with a
directory in worktree_list. If None, we will attempt to calculate
the directories automatically.
kwargs: Leave this here for forward-compatibility.
"""
```

169
docs/windows.md Normal file
View File

@ -0,0 +1,169 @@
# Microsoft Windows Details
Repo is primarily developed on Linux with a lot of users on macOS.
Windows is, unfortunately, not a common platform.
There is support in repo for Windows, but there might be some rough edges.
Keep in mind that Windows in general is "best effort" and "community supported".
That means we don't actively test or verify behavior, but rely heavily on users
to report problems back to us, and to contribute fixes as needed.
[TOC]
## Windows
We only support Windows 10 or newer.
This is largely due to symlinks not being available in older versions, but it's
also due to most developers not using Windows.
We will never add code specific to older versions of Windows.
It might work, but it most likely won't, so please don't bother asking.
## Git worktrees
*** note
**Warning**: Repo's support for Git worktrees is new & experimental.
Please report any bugs and be sure to maintain backups!
***
The Repo 2.4 release introduced support for [Git worktrees][git-worktree].
You don't have to worry about or understand this particular feature, so don't
worry if this section of the Git manual is particularly impenetrable.
The salient point is that Git worktrees allow Repo to create repo client
checkouts that do not require symlinks at all under Windows.
This means users no longer need Administrator access to sync code.
Simply use `--worktree` when running `repo init` to opt in.
This does not effect specific Git repositories that use symlinks themselves.
[git-worktree]: https://git-scm.com/docs/git-worktree
## Symlinks by default
*** note
**NB**: This section applies to the default Repo behavior which does not use
Git worktrees (see the previous section for more info).
***
Repo will use symlinks heavily internally.
On *NIX platforms, this isn't an issue, but Windows makes it a bit difficult.
There are some documents out there for how to do this, but usually the easiest
answer is to run your shell as an Administrator and invoke repo/git in that.
This isn't a great solution, but Windows doesn't make this easy, so here we are.
### Launch Git Bash
If you install Git Bash (see below), you can launch that with appropriate
permissions so that all programs "just work".
* Open the Start Menu (i.e. press the ⊞ key).
* Find/search for "Git Bash".
* Right click it and select "Run as administrator".
*** note
**NB**: This environment is only needed when running `repo`, or any specific `git`
command that might involve symlinks (e.g. `pull` or `checkout`).
You do not need to run all your commands in here such as your editor.
***
### Symlinks with GNU tools
If you want to use `ln -s` inside of the default Git/bash shell, you might need
to export this environment variable:
```sh
$ export MSYS="winsymlinks:nativestrict"
```
Otherwise `ln -s` will copy files and not actually create a symlink.
This also helps `tar` unpack symlinks, so that's nice.
### References
* https://github.com/git-for-windows/git/wiki/Symbolic-Links
* https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/
## Python
Python 3.6 or newer is required.
Python 2 is known to be broken when running under Windows.
See our [Python Support](./python-support.md) document for more details.
You can grab the latest Windows installer here:<br>
https://www.python.org/downloads/release/python-3
## Git
You should install the most recent version of Git for Windows:<br>
https://git-scm.com/download/win
When installing, make sure to turn on "Enable symbolic links" when prompted.
If you've already installed Git for Windows, you can simply download the latest
installer from above and run it again.
It should safely upgrade things in situ for you.
This is useful if you want to switch the symbolic link option after the fact.
## Shell
We don't have a specific requirement for shell environments when running repo.
Most developers use MinTTY/bash that's included with the Git for Windows install
(so see above for installing Git).
Command & Powershell & the Windows Terminal probably work.
Who knows!
## FAQ
### repo upload always complains about allowing hooks or using --no-verify!
When using `repo upload` in projects that have custom repohooks, you might get
an error like the following:
```sh
$ repo upload
ERROR: You must allow the pre-upload hook or use --no-verify.
```
This can be confusing as you never get prompted.
[MinTTY has a bug][mintty] that breaks isatty checking inside of repo which
causes repo to never interactively prompt the user which means the upload check
always fails.
You can workaround this by manually granting consent when uploading.
Simply add the `--verify` option whenever uploading:
```sh
$ repo upload --verify
```
You will have to specify this flag every time you upload.
[mintty]: https://github.com/mintty/mintty/issues/56
### repohooks always fail with an close_fds error.
When using the [reference repohooks project][repohooks] included in AOSP,
you might see errors like this when running `repo upload`:
```sh
$ repo upload
ERROR: Traceback (most recent call last):
...
File "C:\...\lib\subprocess.py", line 351, in __init__
raise ValueError("close_fds is not supported on Windows "
ValueError: close_fds is not supported on Windows platforms if you redirect stdin/stderr/stdout
Failed to run main() for pre-upload hook; see traceback above.
```
This error shows up when using Python 2.
You should upgrade to Python 3 instead (see above).
If you already have Python 3 installed, make sure it's the default version.
Running `python --version` should say `Python 3`, not `Python 2`.
If you didn't install the Python versions, or don't have permission to change
the default version, you can probably workaround this by changing `$PATH` in
your shell so the Python 3 version is found first.
[repohooks]: https://android.googlesource.com/platform/tools/repohooks

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import re
import sys
@ -21,6 +19,8 @@ import subprocess
import tempfile
from error import EditorError
import platform_utils
class Editor(object):
"""Manages the user's preferred text editor."""
@ -55,7 +55,7 @@ class Editor(object):
if os.getenv('TERM') == 'dumb':
print(
"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
"""No editor specified in GIT_EDITOR, core.editor, VISUAL or EDITOR.
Tried to fall back to vi but terminal is dumb. Please configure at
least one of these before using this command.""", file=sys.stderr)
sys.exit(1)
@ -66,11 +66,14 @@ least one of these before using this command.""", file=sys.stderr)
def EditString(cls, data):
"""Opens an editor to edit the given content.
Args:
data : the text to edit
Args:
data: The text to edit.
Returns:
new value of edited text; None if editing did not succeed
Returns:
New value of edited text.
Raises:
EditorError: The editor failed to run.
"""
editor = cls._GetEditor()
if editor == ':':
@ -78,11 +81,16 @@ least one of these before using this command.""", file=sys.stderr)
fd, path = tempfile.mkstemp()
try:
os.write(fd, data)
os.write(fd, data.encode('utf-8'))
os.close(fd)
fd = None
if re.compile("^.*[$ \t'].*$").match(editor):
if platform_utils.isWindows():
# Split on spaces, respecting quoted strings
import shlex
args = shlex.split(editor)
shell = False
elif re.compile("^.*[$ \t'].*$").match(editor):
args = [editor + ' "$@"', 'sh']
shell = True
else:
@ -94,17 +102,14 @@ least one of these before using this command.""", file=sys.stderr)
rc = subprocess.Popen(args, shell=shell).wait()
except OSError as e:
raise EditorError('editor failed, %s: %s %s'
% (str(e), editor, path))
% (str(e), editor, path))
if rc != 0:
raise EditorError('editor failed with exit status %d: %s %s'
% (rc, editor, path))
% (rc, editor, path))
fd2 = open(path)
try:
return fd2.read()
finally:
fd2.close()
with open(path, mode='rb') as fd2:
return fd2.read().decode('utf-8')
finally:
if fd:
os.close(fd)
os.remove(path)
platform_utils.remove(path)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,74 +12,93 @@
# See the License for the specific language governing permissions and
# limitations under the License.
class ManifestParseError(Exception):
"""Failed to parse the manifest file.
"""
class ManifestInvalidRevisionError(Exception):
class ManifestInvalidRevisionError(ManifestParseError):
"""The revision value in a project is incorrect.
"""
class ManifestInvalidPathError(ManifestParseError):
"""A path used in <copyfile> or <linkfile> is incorrect.
"""
class NoManifestException(Exception):
"""The required manifest does not exist.
"""
def __init__(self, path, reason):
super(NoManifestException, self).__init__()
super().__init__(path, reason)
self.path = path
self.reason = reason
def __str__(self):
return self.reason
class EditorError(Exception):
"""Unspecified error from the user's text editor.
"""
def __init__(self, reason):
super(EditorError, self).__init__()
super().__init__(reason)
self.reason = reason
def __str__(self):
return self.reason
class GitError(Exception):
"""Unspecified internal error from git.
"""
def __init__(self, command):
super(GitError, self).__init__()
super().__init__(command)
self.command = command
def __str__(self):
return self.command
class UploadError(Exception):
"""A bundle upload to Gerrit did not succeed.
"""
def __init__(self, reason):
super(UploadError, self).__init__()
super().__init__(reason)
self.reason = reason
def __str__(self):
return self.reason
class DownloadError(Exception):
"""Cannot download a repository.
"""
def __init__(self, reason):
super(DownloadError, self).__init__()
super().__init__(reason)
self.reason = reason
def __str__(self):
return self.reason
class NoSuchProjectError(Exception):
"""A specified project does not exist in the work tree.
"""
def __init__(self, name=None):
super(NoSuchProjectError, self).__init__()
super().__init__(name)
self.name = name
def __str__(self):
if self.Name is None:
if self.name is None:
return 'in current directory'
return self.name
@ -88,24 +106,28 @@ class NoSuchProjectError(Exception):
class InvalidProjectGroupsError(Exception):
"""A specified project is not suitable for the specified groups
"""
def __init__(self, name=None):
super(InvalidProjectGroupsError, self).__init__()
super().__init__(name)
self.name = name
def __str__(self):
if self.Name is None:
if self.name is None:
return 'in current directory'
return self.name
class RepoChangedException(Exception):
"""Thrown if 'repo sync' results in repo updating its internal
repo or manifest repositories. In this special case we must
use exec to re-execute repo with the new code and manifest.
"""
def __init__(self, extra_args=None):
super(RepoChangedException, self).__init__()
super().__init__(extra_args)
self.extra_args = extra_args or []
class HookError(Exception):
"""Thrown if a 'repo-hook' could not be run.

175
event_log.py Normal file
View File

@ -0,0 +1,175 @@
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import multiprocessing
TASK_COMMAND = 'command'
TASK_SYNC_NETWORK = 'sync-network'
TASK_SYNC_LOCAL = 'sync-local'
class EventLog(object):
"""Event log that records events that occurred during a repo invocation.
Events are written to the log as a consecutive JSON entries, one per line.
Each entry contains the following keys:
- id: A ('RepoOp', ID) tuple, suitable for storing in a datastore.
The ID is only unique for the invocation of the repo command.
- name: Name of the object being operated upon.
- task_name: The task that was performed.
- start: Timestamp of when the operation started.
- finish: Timestamp of when the operation finished.
- success: Boolean indicating if the operation was successful.
- try_count: A counter indicating the try count of this task.
Optionally:
- parent: A ('RepoOp', ID) tuple indicating the parent event for nested
events.
Valid task_names include:
- command: The invocation of a subcommand.
- sync-network: The network component of a sync command.
- sync-local: The local component of a sync command.
Specific tasks may include additional informational properties.
"""
def __init__(self):
"""Initializes the event log."""
self._log = []
self._parent = None
def Add(self, name, task_name, start, finish=None, success=None,
try_count=1, kind='RepoOp'):
"""Add an event to the log.
Args:
name: Name of the object being operated upon.
task_name: A sub-task that was performed for name.
start: Timestamp of when the operation started.
finish: Timestamp of when the operation finished.
success: Boolean indicating if the operation was successful.
try_count: A counter indicating the try count of this task.
kind: The kind of the object for the unique identifier.
Returns:
A dictionary of the event added to the log.
"""
event = {
'id': (kind, _NextEventId()),
'name': name,
'task_name': task_name,
'start_time': start,
'try': try_count,
}
if self._parent:
event['parent'] = self._parent['id']
if success is not None or finish is not None:
self.FinishEvent(event, finish, success)
self._log.append(event)
return event
def AddSync(self, project, task_name, start, finish, success):
"""Add a event to the log for a sync command.
Args:
project: Project being synced.
task_name: A sub-task that was performed for name.
One of (TASK_SYNC_NETWORK, TASK_SYNC_LOCAL)
start: Timestamp of when the operation started.
finish: Timestamp of when the operation finished.
success: Boolean indicating if the operation was successful.
Returns:
A dictionary of the event added to the log.
"""
event = self.Add(project.relpath, task_name, start, finish, success)
if event is not None:
event['project'] = project.name
if project.revisionExpr:
event['revision'] = project.revisionExpr
if project.remote.url:
event['project_url'] = project.remote.url
if project.remote.fetchUrl:
event['remote_url'] = project.remote.fetchUrl
try:
event['git_hash'] = project.GetCommitRevisionId()
except Exception:
pass
return event
def GetStatusString(self, success):
"""Converst a boolean success to a status string.
Args:
success: Boolean indicating if the operation was successful.
Returns:
status string.
"""
return 'pass' if success else 'fail'
def FinishEvent(self, event, finish, success):
"""Finishes an incomplete event.
Args:
event: An event that has been added to the log.
finish: Timestamp of when the operation finished.
success: Boolean indicating if the operation was successful.
Returns:
A dictionary of the event added to the log.
"""
event['status'] = self.GetStatusString(success)
event['finish_time'] = finish
return event
def SetParent(self, event):
"""Set a parent event for all new entities.
Args:
event: The event to use as a parent.
"""
self._parent = event
def Write(self, filename):
"""Writes the log out to a file.
Args:
filename: The file to write the log to.
"""
with open(filename, 'w+') as f:
for e in self._log:
json.dump(e, f, sort_keys=True)
f.write('\n')
# An integer id that is unique across this invocation of the program.
_EVENT_ID = multiprocessing.Value('i', 1)
def _NextEventId():
"""Helper function for grabbing the next unique id.
Returns:
A unique, to this invocation of the program, integer id.
"""
with _EVENT_ID.get_lock():
val = _EVENT_ID.value
_EVENT_ID.value += 1
return val

45
fetch.py Normal file
View File

@ -0,0 +1,45 @@
# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module contains functions used to fetch files from various sources."""
import subprocess
import sys
from urllib.parse import urlparse
from urllib.request import urlopen
def fetch_file(url, verbose=False):
"""Fetch a file from the specified source using the appropriate protocol.
Returns:
The contents of the file as bytes.
"""
scheme = urlparse(url).scheme
if scheme == 'gs':
cmd = ['gsutil', 'cat', url]
try:
result = subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
if result.stderr and verbose:
print('warning: non-fatal error running "gsutil": %s' % result.stderr,
file=sys.stderr)
return result.stdout
except subprocess.CalledProcessError as e:
print('fatal: error running "gsutil": %s' % e.stderr,
file=sys.stderr)
sys.exit(1)
with urlopen(url) as f:
return f.read()

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,171 +12,223 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import functools
import os
import sys
import subprocess
import tempfile
from signal import SIGTERM
from error import GitError
from trace import REPO_TRACE, IsTrace, Trace
from git_refs import HEAD
import platform_utils
from repo_trace import REPO_TRACE, IsTrace, Trace
from wrapper import Wrapper
GIT = 'git'
MIN_GIT_VERSION = (1, 5, 4)
# NB: These do not need to be kept in sync with the repo launcher script.
# These may be much newer as it allows the repo launcher to roll between
# different repo releases while source versions might require a newer git.
#
# The soft version is when we start warning users that the version is old and
# we'll be dropping support for it. We'll refuse to work with versions older
# than the hard version.
#
# git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty.
MIN_GIT_VERSION_SOFT = (1, 9, 1)
MIN_GIT_VERSION_HARD = (1, 7, 2)
GIT_DIR = 'GIT_DIR'
LAST_GITDIR = None
LAST_CWD = None
_ssh_proxy_path = None
_ssh_sock_path = None
_ssh_clients = []
def ssh_sock(create=True):
global _ssh_sock_path
if _ssh_sock_path is None:
if not create:
return None
tmp_dir = '/tmp'
if not os.path.exists(tmp_dir):
tmp_dir = tempfile.gettempdir()
_ssh_sock_path = os.path.join(
tempfile.mkdtemp('', 'ssh-', tmp_dir),
'master-%r@%h:%p')
return _ssh_sock_path
def _ssh_proxy():
global _ssh_proxy_path
if _ssh_proxy_path is None:
_ssh_proxy_path = os.path.join(
os.path.dirname(__file__),
'git_ssh')
return _ssh_proxy_path
def _add_ssh_client(p):
_ssh_clients.append(p)
def _remove_ssh_client(p):
try:
_ssh_clients.remove(p)
except ValueError:
pass
def terminate_ssh_clients():
global _ssh_clients
for p in _ssh_clients:
try:
os.kill(p.pid, SIGTERM)
p.wait()
except OSError:
pass
_ssh_clients = []
_git_version = None
class _GitCall(object):
def version(self):
p = GitCommand(None, ['--version'], capture_stdout=True)
if p.Wait() == 0:
return p.stdout
return None
@functools.lru_cache(maxsize=None)
def version_tuple(self):
global _git_version
if _git_version is None:
ver_str = git.version().decode('utf-8')
_git_version = Wrapper().ParseGitVersion(ver_str)
if _git_version is None:
print('fatal: "%s" unsupported' % ver_str, file=sys.stderr)
sys.exit(1)
return _git_version
ret = Wrapper().ParseGitVersion()
if ret is None:
print('fatal: unable to detect git version', file=sys.stderr)
sys.exit(1)
return ret
def __getattr__(self, name):
name = name.replace('_','-')
name = name.replace('_', '-')
def fun(*cmdv):
command = [name]
command.extend(cmdv)
return GitCommand(None, command).Wait() == 0
return fun
git = _GitCall()
def git_require(min_version, fail=False):
def RepoSourceVersion():
"""Return the version of the repo.git tree."""
ver = getattr(RepoSourceVersion, 'version', None)
# We avoid GitCommand so we don't run into circular deps -- GitCommand needs
# to initialize version info we provide.
if ver is None:
env = GitCommand._GetBasicEnv()
proj = os.path.dirname(os.path.abspath(__file__))
env[GIT_DIR] = os.path.join(proj, '.git')
result = subprocess.run([GIT, 'describe', HEAD], stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, encoding='utf-8',
env=env, check=False)
if result.returncode == 0:
ver = result.stdout.strip()
if ver.startswith('v'):
ver = ver[1:]
else:
ver = 'unknown'
setattr(RepoSourceVersion, 'version', ver)
return ver
class UserAgent(object):
"""Mange User-Agent settings when talking to external services
We follow the style as documented here:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
"""
_os = None
_repo_ua = None
_git_ua = None
@property
def os(self):
"""The operating system name."""
if self._os is None:
os_name = sys.platform
if os_name.lower().startswith('linux'):
os_name = 'Linux'
elif os_name == 'win32':
os_name = 'Win32'
elif os_name == 'cygwin':
os_name = 'Cygwin'
elif os_name == 'darwin':
os_name = 'Darwin'
self._os = os_name
return self._os
@property
def repo(self):
"""The UA when connecting directly from repo."""
if self._repo_ua is None:
py_version = sys.version_info
self._repo_ua = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
RepoSourceVersion(),
self.os,
git.version_tuple().full,
py_version.major, py_version.minor, py_version.micro)
return self._repo_ua
@property
def git(self):
"""The UA when running git."""
if self._git_ua is None:
self._git_ua = 'git/%s (%s) git-repo/%s' % (
git.version_tuple().full,
self.os,
RepoSourceVersion())
return self._git_ua
user_agent = UserAgent()
def git_require(min_version, fail=False, msg=''):
git_version = git.version_tuple()
if min_version <= git_version:
return True
if fail:
need = '.'.join(map(str, min_version))
print('fatal: git %s or later required' % need, file=sys.stderr)
if msg:
msg = ' for ' + msg
print('fatal: git %s or later required%s' % (need, msg), file=sys.stderr)
sys.exit(1)
return False
def _setenv(env, name, value):
env[name] = value.encode()
class GitCommand(object):
"""Wrapper around a single git invocation."""
def __init__(self,
project,
cmdv,
bare = False,
provide_stdin = False,
capture_stdout = False,
capture_stderr = False,
disable_editor = False,
ssh_proxy = False,
cwd = None,
gitdir = None):
env = os.environ.copy()
for key in [REPO_TRACE,
GIT_DIR,
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_OBJECT_DIRECTORY',
'GIT_WORK_TREE',
'GIT_GRAFT_FILE',
'GIT_INDEX_FILE']:
if key in env:
del env[key]
bare=False,
input=None,
capture_stdout=False,
capture_stderr=False,
merge_output=False,
disable_editor=False,
ssh_proxy=None,
cwd=None,
gitdir=None,
objdir=None):
env = self._GetBasicEnv()
if disable_editor:
_setenv(env, 'GIT_EDITOR', ':')
env['GIT_EDITOR'] = ':'
if ssh_proxy:
_setenv(env, 'REPO_SSH_SOCK', ssh_sock())
_setenv(env, 'GIT_SSH', _ssh_proxy())
env['REPO_SSH_SOCK'] = ssh_proxy.sock()
env['GIT_SSH'] = ssh_proxy.proxy
env['GIT_SSH_VARIANT'] = 'ssh'
if 'http_proxy' in env and 'darwin' == sys.platform:
s = "'http.proxy=%s'" % (env['http_proxy'],)
p = env.get('GIT_CONFIG_PARAMETERS')
if p is not None:
s = p + ' ' + s
_setenv(env, 'GIT_CONFIG_PARAMETERS', s)
env['GIT_CONFIG_PARAMETERS'] = s
if 'GIT_ALLOW_PROTOCOL' not in env:
env['GIT_ALLOW_PROTOCOL'] = (
'file:git:http:https:ssh:persistent-http:persistent-https:sso:rpc')
env['GIT_HTTP_USER_AGENT'] = user_agent.git
if project:
if not cwd:
cwd = project.worktree
if not gitdir:
gitdir = project.gitdir
# Git on Windows wants its paths only using / for reliability.
if platform_utils.isWindows():
if objdir:
objdir = objdir.replace('\\', '/')
if gitdir:
gitdir = gitdir.replace('\\', '/')
if objdir:
# Set to the place we want to save the objects.
env['GIT_OBJECT_DIRECTORY'] = objdir
if gitdir:
# Allow git to search the original place in case of local or unique refs
# that git will attempt to resolve even if we aren't fetching them.
env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = gitdir + '/objects'
command = [GIT]
if bare:
if gitdir:
_setenv(env, GIT_DIR, gitdir)
env[GIT_DIR] = gitdir
cwd = None
command.extend(cmdv)
command.append(cmdv[0])
# Need to use the --progress flag for fetch/clone so output will be
# displayed as by default git only does progress output if stderr is a TTY.
if sys.stderr.isatty() and cmdv[0] in ('fetch', 'clone'):
if '--progress' not in cmdv and '--quiet' not in cmdv:
command.append('--progress')
command.extend(cmdv[1:])
if provide_stdin:
stdin = subprocess.PIPE
else:
stdin = None
if capture_stdout:
stdout = subprocess.PIPE
else:
stdout = None
if capture_stderr:
stderr = subprocess.PIPE
else:
stderr = None
stdin = subprocess.PIPE if input else None
stdout = subprocess.PIPE if capture_stdout else None
stderr = (subprocess.STDOUT if merge_output else
(subprocess.PIPE if capture_stderr else None))
if IsTrace():
global LAST_CWD
@ -197,6 +248,11 @@ class GitCommand(object):
dbg += ': export GIT_DIR=%s\n' % env[GIT_DIR]
LAST_GITDIR = env[GIT_DIR]
if 'GIT_OBJECT_DIRECTORY' in env:
dbg += ': export GIT_OBJECT_DIRECTORY=%s\n' % env['GIT_OBJECT_DIRECTORY']
if 'GIT_ALTERNATE_OBJECT_DIRECTORIES' in env:
dbg += ': export GIT_ALTERNATE_OBJECT_DIRECTORIES=%s\n' % env['GIT_ALTERNATE_OBJECT_DIRECTORIES']
dbg += ': '
dbg += ' '.join(command)
if stdin == subprocess.PIPE:
@ -205,29 +261,50 @@ class GitCommand(object):
dbg += ' 1>|'
if stderr == subprocess.PIPE:
dbg += ' 2>|'
elif stderr == subprocess.STDOUT:
dbg += ' 2>&1'
Trace('%s', dbg)
try:
p = subprocess.Popen(command,
cwd = cwd,
env = env,
stdin = stdin,
stdout = stdout,
stderr = stderr)
cwd=cwd,
env=env,
encoding='utf-8',
errors='backslashreplace',
stdin=stdin,
stdout=stdout,
stderr=stderr)
except Exception as e:
raise GitError('%s: %s' % (command[1], e))
if ssh_proxy:
_add_ssh_client(p)
ssh_proxy.add_client(p)
self.process = p
self.stdin = p.stdin
try:
self.stdout, self.stderr = p.communicate(input=input)
finally:
if ssh_proxy:
ssh_proxy.remove_client(p)
self.rc = p.wait()
@staticmethod
def _GetBasicEnv():
"""Return a basic env for running git under.
This is guaranteed to be side-effect free.
"""
env = os.environ.copy()
for key in (REPO_TRACE,
GIT_DIR,
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_OBJECT_DIRECTORY',
'GIT_WORK_TREE',
'GIT_GRAFT_FILE',
'GIT_INDEX_FILE'):
env.pop(key, None)
return env
def Wait(self):
try:
p = self.process
(self.stdout, self.stderr) = p.communicate()
rc = p.returncode
finally:
_remove_ssh_client(p)
return rc
return self.rc

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,74 +12,85 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import contextlib
import datetime
import errno
from http.client import HTTPException
import json
import os
import pickle
import re
import ssl
import subprocess
import sys
try:
import threading as _threading
except ImportError:
import dummy_threading as _threading
import time
import urllib.error
import urllib.request
from pyversion import is_python3
if is_python3():
import urllib.request
import urllib.error
else:
import urllib2
import imp
urllib = imp.new_module('urllib')
urllib.request = urllib2
urllib.error = urllib2
from signal import SIGTERM
from error import GitError, UploadError
from trace import Trace
if is_python3():
from http.client import HTTPException
else:
from httplib import HTTPException
import platform_utils
from repo_trace import Trace
from git_command import GitCommand
from git_command import ssh_sock
from git_command import terminate_ssh_clients
from git_refs import R_CHANGES, R_HEADS, R_TAGS
# Prefix that is prepended to all the keys of SyncAnalysisState's data
# that is saved in the config.
SYNC_STATE_PREFIX = 'repo.syncstate.'
R_HEADS = 'refs/heads/'
R_TAGS = 'refs/tags/'
ID_RE = re.compile(r'^[0-9a-f]{40}$')
REVIEW_CACHE = dict()
def IsChange(rev):
return rev.startswith(R_CHANGES)
def IsId(rev):
return ID_RE.match(rev)
def IsTag(rev):
return rev.startswith(R_TAGS)
def IsImmutable(rev):
return IsChange(rev) or IsId(rev) or IsTag(rev)
def _key(name):
parts = name.split('.')
if len(parts) < 2:
return name.lower()
parts[ 0] = parts[ 0].lower()
parts[0] = parts[0].lower()
parts[-1] = parts[-1].lower()
return '.'.join(parts)
class GitConfig(object):
_ForUser = None
_USER_CONFIG = '~/.gitconfig'
_ForSystem = None
_SYSTEM_CONFIG = '/etc/gitconfig'
@classmethod
def ForSystem(cls):
if cls._ForSystem is None:
cls._ForSystem = cls(configfile=cls._SYSTEM_CONFIG)
return cls._ForSystem
@classmethod
def ForUser(cls):
if cls._ForUser is None:
cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
cls._ForUser = cls(configfile=os.path.expanduser(cls._USER_CONFIG))
return cls._ForUser
@classmethod
def ForRepository(cls, gitdir, defaults=None):
return cls(configfile = os.path.join(gitdir, 'config'),
defaults = defaults)
return cls(configfile=os.path.join(gitdir, 'config'),
defaults=defaults)
def __init__(self, configfile, defaults=None, pickleFile=None):
def __init__(self, configfile, defaults=None, jsonFile=None):
self.file = configfile
self.defaults = defaults
self._cache_dict = None
@ -88,22 +98,77 @@ class GitConfig(object):
self._remotes = {}
self._branches = {}
if pickleFile is None:
self._pickle = os.path.join(
os.path.dirname(self.file),
'.repopickle_' + os.path.basename(self.file))
else:
self._pickle = pickleFile
self._json = jsonFile
if self._json is None:
self._json = os.path.join(
os.path.dirname(self.file),
'.repo_' + os.path.basename(self.file) + '.json')
def Has(self, name, include_defaults = True):
def ClearCache(self):
"""Clear the in-memory cache of config."""
self._cache_dict = None
def Has(self, name, include_defaults=True):
"""Return true if this configuration file has the key.
"""
if _key(name) in self._cache:
return True
if include_defaults and self.defaults:
return self.defaults.Has(name, include_defaults = True)
return self.defaults.Has(name, include_defaults=True)
return False
def GetInt(self, name):
"""Returns an integer from the configuration file.
This follows the git config syntax.
Args:
name: The key to lookup.
Returns:
None if the value was not defined, or is not a boolean.
Otherwise, the number itself.
"""
v = self.GetString(name)
if v is None:
return None
v = v.strip()
mult = 1
if v.endswith('k'):
v = v[:-1]
mult = 1024
elif v.endswith('m'):
v = v[:-1]
mult = 1024 * 1024
elif v.endswith('g'):
v = v[:-1]
mult = 1024 * 1024 * 1024
base = 10
if v.startswith('0x'):
base = 16
try:
return int(v, base=base) * mult
except ValueError:
return None
def DumpConfigDict(self):
"""Returns the current configuration dict.
Configuration data is information only (e.g. logging) and
should not be considered a stable data-source.
Returns:
dict of {<key>, <value>} for git configuration cache.
<value> are strings converted by GetString.
"""
config_dict = {}
for key in self._cache:
config_dict[key] = self.GetString(key)
return config_dict
def GetBoolean(self, name):
"""Returns a boolean from the configuration file.
None : The value was not defined, or is not a boolean.
@ -120,6 +185,12 @@ class GitConfig(object):
return False
return None
def SetBoolean(self, name, value):
"""Set the truthy value for a key."""
if value is not None:
value = 'true' if value else 'false'
self.SetString(name, value)
def GetString(self, name, all_keys=False):
"""Get the first value for a key, or None if it is not defined.
@ -130,7 +201,7 @@ class GitConfig(object):
v = self._cache[_key(name)]
except KeyError:
if self.defaults:
return self.defaults.GetString(name, all_keys = all_keys)
return self.defaults.GetString(name, all_keys=all_keys)
v = []
if not all_keys:
@ -141,7 +212,7 @@ class GitConfig(object):
r = []
r.extend(v)
if self.defaults:
r.extend(self.defaults.GetString(name, all_keys = True))
r.extend(self.defaults.GetString(name, all_keys=True))
return r
def SetString(self, name, value):
@ -200,12 +271,28 @@ class GitConfig(object):
self._branches[b.name] = b
return b
def GetSyncAnalysisStateData(self):
"""Returns data to be logged for the analysis of sync performance."""
return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
def UpdateSyncAnalysisState(self, options, superproject_logging_data):
"""Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
Args:
options: Options passed to sync returned from optparse. See _Options().
superproject_logging_data: A dictionary of superproject data that is to be logged.
Returns:
SyncAnalysisState object.
"""
return SyncAnalysisState(self, options, superproject_logging_data)
def GetSubSections(self, section):
"""List all subsection names matching $section.*.*
"""
return self._sections.get(section, set())
def HasSection(self, section, subsection = ''):
def HasSection(self, section, subsection=''):
"""Does at least one key in section.subsection exist?
"""
try:
@ -217,9 +304,9 @@ class GitConfig(object):
"""Resolve any url.*.insteadof references.
"""
for new_url in self.GetSubSections('url'):
old_url = self.GetString('url.%s.insteadof' % new_url)
if old_url is not None and url.startswith(old_url):
return new_url + url[len(old_url):]
for old_url in self.GetString('url.%s.insteadof' % new_url, True):
if old_url is not None and url.startswith(old_url):
return new_url + url[len(old_url):]
return url
@property
@ -248,50 +335,33 @@ class GitConfig(object):
return self._cache_dict
def _Read(self):
d = self._ReadPickle()
d = self._ReadJson()
if d is None:
d = self._ReadGit()
self._SavePickle(d)
self._SaveJson(d)
return d
def _ReadPickle(self):
def _ReadJson(self):
try:
if os.path.getmtime(self._pickle) \
<= os.path.getmtime(self.file):
os.remove(self._pickle)
if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
platform_utils.remove(self._json)
return None
except OSError:
return None
try:
Trace(': unpickle %s', self.file)
fd = open(self._pickle, 'rb')
try:
return pickle.load(fd)
finally:
fd.close()
except EOFError:
os.remove(self._pickle)
return None
except IOError:
os.remove(self._pickle)
return None
except pickle.PickleError:
os.remove(self._pickle)
Trace(': parsing %s', self.file)
with open(self._json) as fd:
return json.load(fd)
except (IOError, ValueError):
platform_utils.remove(self._json, missing_ok=True)
return None
def _SavePickle(self, cache):
def _SaveJson(self, cache):
try:
fd = open(self._pickle, 'wb')
try:
pickle.dump(cache, fd, pickle.HIGHEST_PROTOCOL)
finally:
fd.close()
except IOError:
if os.path.exists(self._pickle):
os.remove(self._pickle)
except pickle.PickleError:
if os.path.exists(self._pickle):
os.remove(self._pickle)
with open(self._json, 'w') as fd:
json.dump(cache, fd, indent=2)
except (IOError, TypeError):
platform_utils.remove(self._json, missing_ok=True)
def _ReadGit(self):
"""
@ -301,11 +371,11 @@ class GitConfig(object):
"""
c = {}
d = self._do('--null', '--list')
if d is None:
if not os.path.exists(self.file):
return c
for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
# Backslash is not anomalous
d = self._do('--null', '--list')
for line in d.rstrip('\0').split('\0'):
if '\n' in line:
key, val = line.split('\n', 1)
else:
@ -320,17 +390,26 @@ class GitConfig(object):
return c
def _do(self, *args):
command = ['config', '--file', self.file]
if self.file == self._SYSTEM_CONFIG:
command = ['config', '--system', '--includes']
else:
command = ['config', '--file', self.file, '--includes']
command.extend(args)
p = GitCommand(None,
command,
capture_stdout = True,
capture_stderr = True)
capture_stdout=True,
capture_stderr=True)
if p.Wait() == 0:
return p.stdout
else:
GitError('git config %s: %s' % (str(args), p.stderr))
raise GitError('git config %s: %s' % (str(args), p.stderr))
class RepoConfig(GitConfig):
"""User settings for repo itself."""
_USER_CONFIG = '~/.repoconfig/config'
class RefSpec(object):
@ -389,160 +468,70 @@ class RefSpec(object):
return s
_master_processes = []
_master_keys = set()
_ssh_master = True
_master_keys_lock = None
def init_ssh():
"""Should be called once at the start of repo to init ssh master handling.
At the moment, all we do is to create our lock.
"""
global _master_keys_lock
assert _master_keys_lock is None, "Should only call init_ssh once"
_master_keys_lock = _threading.Lock()
def _open_ssh(host, port=None):
global _ssh_master
# Acquire the lock. This is needed to prevent opening multiple masters for
# the same host when we're running "repo sync -jN" (for N > 1) _and_ the
# manifest <remote fetch="ssh://xyz"> specifies a different host from the
# one that was passed to repo init.
_master_keys_lock.acquire()
try:
# Check to see whether we already think that the master is running; if we
# think it's already running, return right away.
if port is not None:
key = '%s:%s' % (host, port)
else:
key = host
if key in _master_keys:
return True
if not _ssh_master \
or 'GIT_SSH' in os.environ \
or sys.platform in ('win32', 'cygwin'):
# failed earlier, or cygwin ssh can't do this
#
return False
# We will make two calls to ssh; this is the common part of both calls.
command_base = ['ssh',
'-o','ControlPath %s' % ssh_sock(),
host]
if port is not None:
command_base[1:1] = ['-p', str(port)]
# Since the key wasn't in _master_keys, we think that master isn't running.
# ...but before actually starting a master, we'll double-check. This can
# be important because we can't tell that that 'git@myhost.com' is the same
# as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
check_command = command_base + ['-O','check']
try:
Trace(': %s', ' '.join(check_command))
check_process = subprocess.Popen(check_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
check_process.communicate() # read output, but ignore it...
isnt_running = check_process.wait()
if not isnt_running:
# Our double-check found that the master _was_ infact running. Add to
# the list of keys.
_master_keys.add(key)
return True
except Exception:
# Ignore excpetions. We we will fall back to the normal command and print
# to the log there.
pass
command = command_base[:1] + \
['-M', '-N'] + \
command_base[1:]
try:
Trace(': %s', ' '.join(command))
p = subprocess.Popen(command)
except Exception as e:
_ssh_master = False
print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
% (host,port, str(e)), file=sys.stderr)
return False
_master_processes.append(p)
_master_keys.add(key)
time.sleep(1)
return True
finally:
_master_keys_lock.release()
def close_ssh():
global _master_keys_lock
terminate_ssh_clients()
for p in _master_processes:
try:
os.kill(p.pid, SIGTERM)
p.wait()
except OSError:
pass
del _master_processes[:]
_master_keys.clear()
d = ssh_sock(create=False)
if d:
try:
os.rmdir(os.path.dirname(d))
except OSError:
pass
# We're done with the lock, so we can delete it.
_master_keys_lock = None
URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
def GetSchemeFromUrl(url):
m = URI_ALL.match(url)
if m:
return m.group(1)
return None
def _preconnect(url):
m = URI_ALL.match(url)
if m:
scheme = m.group(1)
host = m.group(2)
if ':' in host:
host, port = host.split(':')
else:
port = None
if scheme in ('ssh', 'git+ssh', 'ssh+git'):
return _open_ssh(host, port)
return False
m = URI_SCP.match(url)
if m:
host = m.group(1)
return _open_ssh(host)
@contextlib.contextmanager
def GetUrlCookieFile(url, quiet):
if url.startswith('persistent-'):
try:
p = subprocess.Popen(
['git-remote-persistent-https', '-print_config', url],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
try:
cookieprefix = 'http.cookiefile='
proxyprefix = 'http.proxy='
cookiefile = None
proxy = None
for line in p.stdout:
line = line.strip().decode('utf-8')
if line.startswith(cookieprefix):
cookiefile = os.path.expanduser(line[len(cookieprefix):])
if line.startswith(proxyprefix):
proxy = line[len(proxyprefix):]
# Leave subprocess open, as cookie file may be transient.
if cookiefile or proxy:
yield cookiefile, proxy
return
finally:
p.stdin.close()
if p.wait():
err_msg = p.stderr.read().decode('utf-8')
if ' -print_config' in err_msg:
pass # Persistent proxy doesn't support -print_config.
elif not quiet:
print(err_msg, file=sys.stderr)
except OSError as e:
if e.errno == errno.ENOENT:
pass # No persistent proxy.
raise
cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
if cookiefile:
cookiefile = os.path.expanduser(cookiefile)
yield cookiefile, None
return False
class Remote(object):
"""Configuration options related to a remote.
"""
def __init__(self, config, name):
self._config = config
self.name = name
self.url = self._Get('url')
self.pushUrl = self._Get('pushurl')
self.review = self._Get('review')
self.projectname = self._Get('projectname')
self.fetch = list(map(RefSpec.FromString,
self._Get('fetch', all_keys=True)))
self._Get('fetch', all_keys=True)))
self._review_url = None
def _InsteadOf(self):
@ -556,8 +545,8 @@ class Remote(object):
insteadOfList = globCfg.GetString(key, all_keys=True)
for insteadOf in insteadOfList:
if self.url.startswith(insteadOf) \
and len(insteadOf) > len(longest):
if (self.url.startswith(insteadOf)
and len(insteadOf) > len(longest)):
longest = insteadOf
longestUrl = url
@ -566,17 +555,33 @@ class Remote(object):
return self.url.replace(longest, longestUrl, 1)
def PreConnectFetch(self):
connectionUrl = self._InsteadOf()
return _preconnect(connectionUrl)
def PreConnectFetch(self, ssh_proxy):
"""Run any setup for this remote before we connect to it.
def ReviewUrl(self, userEmail):
In practice, if the remote is using SSH, we'll attempt to create a new
SSH master session to it for reuse across projects.
Args:
ssh_proxy: The SSH settings for managing master sessions.
Returns:
Whether the preconnect phase for this remote was successful.
"""
if not ssh_proxy:
return True
connectionUrl = self._InsteadOf()
return ssh_proxy.preconnect(connectionUrl)
def ReviewUrl(self, userEmail, validate_certs):
if self._review_url is None:
if self.review is None:
return None
u = self.review
if u.split(':')[0] not in ('http', 'https', 'sso'):
if u.startswith('persistent-'):
u = u[len('persistent-'):]
if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
u = 'http://%s' % u
if u.endswith('/Gerrit'):
u = u[:len(u) - len('/Gerrit')]
@ -592,20 +597,28 @@ class Remote(object):
host, port = os.environ['REPO_HOST_PORT_INFO'].split()
self._review_url = self._SshReviewUrl(userEmail, host, port)
REVIEW_CACHE[u] = self._review_url
elif u.startswith('sso:'):
elif u.startswith('sso:') or u.startswith('ssh:'):
self._review_url = u # Assume it's right
REVIEW_CACHE[u] = self._review_url
elif 'REPO_IGNORE_SSH_INFO' in os.environ:
self._review_url = http_url
REVIEW_CACHE[u] = self._review_url
else:
try:
info_url = u + 'ssh_info'
info = urllib.request.urlopen(info_url).read()
if info == 'NOT_AVAILABLE' or '<' in info:
if not validate_certs:
context = ssl._create_unverified_context()
info = urllib.request.urlopen(info_url, context=context).read()
else:
info = urllib.request.urlopen(info_url).read()
if info == b'NOT_AVAILABLE' or b'<' in info:
# If `info` contains '<', we assume the server gave us some sort
# of HTML response back, like maybe a login page.
#
# Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
self._review_url = http_url
else:
info = info.decode('utf-8')
host, port = info.split()
self._review_url = self._SshReviewUrl(userEmail, host, port)
except urllib.error.HTTPError as e:
@ -627,9 +640,7 @@ class Remote(object):
def ToLocal(self, rev):
"""Convert a remote revision string to something we have locally.
"""
if IsId(rev):
return rev
if rev.startswith(R_TAGS):
if self.name == '.' or IsId(rev):
return rev
if not rev.startswith('refs/'):
@ -638,7 +649,12 @@ class Remote(object):
for spec in self.fetch:
if spec.SourceMatches(rev):
return spec.MapSource(rev)
raise GitError('remote %s does not have %s' % (self.name, rev))
if not rev.startswith(R_HEADS):
return rev
raise GitError('%s: remote %s does not have %s' %
(self.projectname, self.name, rev))
def WritesTo(self, ref):
"""True if the remote stores to the tracking ref.
@ -661,6 +677,10 @@ class Remote(object):
"""Save this remote to the configuration.
"""
self._Set('url', self.url)
if self.pushUrl is not None:
self._Set('pushurl', self.pushUrl + '/' + self.projectname)
else:
self._Set('pushurl', self.pushUrl)
self._Set('review', self.review)
self._Set('projectname', self.projectname)
self._Set('fetch', list(map(str, self.fetch)))
@ -671,12 +691,13 @@ class Remote(object):
def _Get(self, key, all_keys=False):
key = 'remote.%s.%s' % (self.name, key)
return self._config.GetString(key, all_keys = all_keys)
return self._config.GetString(key, all_keys=all_keys)
class Branch(object):
"""Configuration options related to a single branch.
"""
def __init__(self, config, name):
self._config = config
self.name = name
@ -707,15 +728,12 @@ class Branch(object):
self._Set('merge', self.merge)
else:
fd = open(self._config.file, 'ab')
try:
with open(self._config.file, 'a') as fd:
fd.write('[branch "%s"]\n' % self.name)
if self.remote:
fd.write('\tremote = %s\n' % self.remote.name)
if self.merge:
fd.write('\tmerge = %s\n' % self.merge)
finally:
fd.close()
def _Set(self, key, value):
key = 'branch.%s.%s' % (self.name, key)
@ -723,4 +741,71 @@ class Branch(object):
def _Get(self, key, all_keys=False):
key = 'branch.%s.%s' % (self.name, key)
return self._config.GetString(key, all_keys = all_keys)
return self._config.GetString(key, all_keys=all_keys)
class SyncAnalysisState:
"""Configuration options related to logging of sync state for analysis.
This object is versioned.
"""
def __init__(self, config, options, superproject_logging_data):
"""Initializes SyncAnalysisState.
Saves the following data into the |config| object.
- sys.argv, options, superproject's logging data.
- repo.*, branch.* and remote.* parameters from config object.
- Current time as synctime.
- Version number of the object.
All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
Args:
config: GitConfig object to store all options.
options: Options passed to sync returned from optparse. See _Options().
superproject_logging_data: A dictionary of superproject data that is to be logged.
"""
self._config = config
now = datetime.datetime.utcnow()
self._Set('main.synctime', now.isoformat() + 'Z')
self._Set('main.version', '1')
self._Set('sys.argv', sys.argv)
for key, value in superproject_logging_data.items():
self._Set(f'superproject.{key}', value)
for key, value in options.__dict__.items():
self._Set(f'options.{key}', value)
config_items = config.DumpConfigDict().items()
EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
self._SetDictionary({k: v for k, v in config_items
if not k.startswith(SYNC_STATE_PREFIX) and
k.split('.', 1)[0] in EXTRACT_NAMESPACES})
def _SetDictionary(self, data):
"""Save all key/value pairs of |data| dictionary.
Args:
data: A dictionary whose key/value are to be saved.
"""
for key, value in data.items():
self._Set(key, value)
def _Set(self, key, value):
"""Set the |value| for a |key| in the |_config| member.
|key| is prepended with the value of SYNC_STATE_PREFIX constant.
Args:
key: Name of the key.
value: |value| could be of any type. If it is 'bool', it will be saved
as a Boolean and for all other types, it will be saved as a String.
"""
if value is None:
return
sync_key = f'{SYNC_STATE_PREFIX}{key}'
sync_key = sync_key.replace('_', '')
if isinstance(value, str):
self._config.SetString(sync_key, value)
elif isinstance(value, bool):
self._config.SetBoolean(sync_key, value)
else:
self._config.SetString(sync_key, str(value))

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -14,13 +13,17 @@
# limitations under the License.
import os
from trace import Trace
from repo_trace import Trace
import platform_utils
HEAD = 'HEAD'
HEAD = 'HEAD'
R_CHANGES = 'refs/changes/'
R_HEADS = 'refs/heads/'
R_TAGS = 'refs/tags/'
R_PUB = 'refs/published/'
R_M = 'refs/remotes/m/'
R_TAGS = 'refs/tags/'
R_PUB = 'refs/published/'
R_WORKTREE = 'refs/worktree/'
R_WORKTREE_M = R_WORKTREE + 'm/'
R_M = 'refs/remotes/m/'
class GitRefs(object):
@ -126,30 +129,26 @@ class GitRefs(object):
def _ReadLoose(self, prefix):
base = os.path.join(self._gitdir, prefix)
for name in os.listdir(base):
for name in platform_utils.listdir(base):
p = os.path.join(base, name)
if os.path.isdir(p):
# We don't implement the full ref validation algorithm, just the simple
# rules that would show up in local filesystems.
# https://git-scm.com/docs/git-check-ref-format
if name.startswith('.') or name.endswith('.lock'):
pass
elif platform_utils.isdir(p):
self._mtime[prefix] = os.path.getmtime(base)
self._ReadLoose(prefix + name + '/')
elif name.endswith('.lock'):
pass
else:
self._ReadLoose1(p, prefix + name)
def _ReadLoose1(self, path, name):
try:
fd = open(path, 'rb')
except IOError:
return
try:
try:
with open(path) as fd:
mtime = os.path.getmtime(path)
ref_id = fd.readline()
except (IOError, OSError):
return
finally:
fd.close()
except (OSError, UnicodeError):
return
try:
ref_id = ref_id.decode()

15
git_ssh
View File

@ -1,2 +1,17 @@
#!/bin/sh
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
exec ssh -o "ControlMaster no" -o "ControlPath $REPO_SSH_SOCK" "$@"

453
git_superproject.py Normal file
View File

@ -0,0 +1,453 @@
# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Provide functionality to get all projects and their commit ids from Superproject.
For more information on superproject, check out:
https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
Examples:
superproject = Superproject(manifest, name, remote, revision)
UpdateProjectsResult = superproject.UpdateProjectsRevisionId(projects)
"""
import hashlib
import functools
import os
import sys
import time
from typing import NamedTuple
from git_command import git_require, GitCommand
from git_config import RepoConfig
from git_refs import R_HEADS
_SUPERPROJECT_GIT_NAME = 'superproject.git'
_SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml'
class SyncResult(NamedTuple):
"""Return the status of sync and whether caller should exit."""
# Whether the superproject sync was successful.
success: bool
# Whether the caller should exit.
fatal: bool
class CommitIdsResult(NamedTuple):
"""Return the commit ids and whether caller should exit."""
# A dictionary with the projects/commit ids on success, otherwise None.
commit_ids: dict
# Whether the caller should exit.
fatal: bool
class UpdateProjectsResult(NamedTuple):
"""Return the overriding manifest file and whether caller should exit."""
# Path name of the overriding manifest file if successful, otherwise None.
manifest_path: str
# Whether the caller should exit.
fatal: bool
class Superproject(object):
"""Get commit ids from superproject.
Initializes a local copy of a superproject for the manifest. This allows
lookup of commit ids for all projects. It contains _project_commit_ids which
is a dictionary with project/commit id entries.
"""
def __init__(self, manifest, name, remote, revision,
superproject_dir='exp-superproject'):
"""Initializes superproject.
Args:
manifest: A Manifest object that is to be written to a file.
name: The unique name of the superproject
remote: The RemoteSpec for the remote.
revision: The name of the git branch to track.
superproject_dir: Relative path under |manifest.subdir| to checkout
superproject.
"""
self._project_commit_ids = None
self._manifest = manifest
self.name = name
self.remote = remote
self.revision = self._branch = revision
self._repodir = manifest.repodir
self._superproject_dir = superproject_dir
self._superproject_path = manifest.SubmanifestInfoDir(manifest.path_prefix,
superproject_dir)
self._manifest_path = os.path.join(self._superproject_path,
_SUPERPROJECT_MANIFEST_NAME)
git_name = hashlib.md5(remote.name.encode('utf8')).hexdigest() + '-'
self._remote_url = remote.url
self._work_git_name = git_name + _SUPERPROJECT_GIT_NAME
self._work_git = os.path.join(self._superproject_path, self._work_git_name)
# The following are command arguemnts, rather than superproject attributes,
# and were included here originally. They should eventually become
# arguments that are passed down from the public methods, instead of being
# treated as attributes.
self._git_event_log = None
self._quiet = False
self._print_messages = False
def SetQuiet(self, value):
"""Set the _quiet attribute."""
self._quiet = value
def SetPrintMessages(self, value):
"""Set the _print_messages attribute."""
self._print_messages = value
@property
def project_commit_ids(self):
"""Returns a dictionary of projects and their commit ids."""
return self._project_commit_ids
@property
def manifest_path(self):
"""Returns the manifest path if the path exists or None."""
return self._manifest_path if os.path.exists(self._manifest_path) else None
def _LogMessage(self, message):
"""Logs message to stderr and _git_event_log."""
if self._print_messages:
print(message, file=sys.stderr)
self._git_event_log.ErrorEvent(message, f'{message}')
def _LogMessagePrefix(self):
"""Returns the prefix string to be logged in each log message"""
return f'repo superproject branch: {self._branch} url: {self._remote_url}'
def _LogError(self, message):
"""Logs error message to stderr and _git_event_log."""
self._LogMessage(f'{self._LogMessagePrefix()} error: {message}')
def _LogWarning(self, message):
"""Logs warning message to stderr and _git_event_log."""
self._LogMessage(f'{self._LogMessagePrefix()} warning: {message}')
def _Init(self):
"""Sets up a local Git repository to get a copy of a superproject.
Returns:
True if initialization is successful, or False.
"""
if not os.path.exists(self._superproject_path):
os.mkdir(self._superproject_path)
if not self._quiet and not os.path.exists(self._work_git):
print('%s: Performing initial setup for superproject; this might take '
'several minutes.' % self._work_git)
cmd = ['init', '--bare', self._work_git_name]
p = GitCommand(None,
cmd,
cwd=self._superproject_path,
capture_stdout=True,
capture_stderr=True)
retval = p.Wait()
if retval:
self._LogWarning(f'git init call failed, command: git {cmd}, '
f'return code: {retval}, stderr: {p.stderr}')
return False
return True
def _Fetch(self):
"""Fetches a local copy of a superproject for the manifest based on |_remote_url|.
Returns:
True if fetch is successful, or False.
"""
if not os.path.exists(self._work_git):
self._LogWarning(f'git fetch missing directory: {self._work_git}')
return False
if not git_require((2, 28, 0)):
self._LogWarning('superproject requires a git version 2.28 or later')
return False
cmd = ['fetch', self._remote_url, '--depth', '1', '--force', '--no-tags',
'--filter', 'blob:none']
if self._branch:
cmd += [self._branch + ':' + self._branch]
p = GitCommand(None,
cmd,
cwd=self._work_git,
capture_stdout=True,
capture_stderr=True)
retval = p.Wait()
if retval:
self._LogWarning(f'git fetch call failed, command: git {cmd}, '
f'return code: {retval}, stderr: {p.stderr}')
return False
return True
def _LsTree(self):
"""Gets the commit ids for all projects.
Works only in git repositories.
Returns:
data: data returned from 'git ls-tree ...' instead of None.
"""
if not os.path.exists(self._work_git):
self._LogWarning(f'git ls-tree missing directory: {self._work_git}')
return None
data = None
branch = 'HEAD' if not self._branch else self._branch
cmd = ['ls-tree', '-z', '-r', branch]
p = GitCommand(None,
cmd,
cwd=self._work_git,
capture_stdout=True,
capture_stderr=True)
retval = p.Wait()
if retval == 0:
data = p.stdout
else:
self._LogWarning(f'git ls-tree call failed, command: git {cmd}, '
f'return code: {retval}, stderr: {p.stderr}')
return data
def Sync(self, git_event_log):
"""Gets a local copy of a superproject for the manifest.
Args:
git_event_log: an EventLog, for git tracing.
Returns:
SyncResult
"""
self._git_event_log = git_event_log
if not self._manifest.superproject:
self._LogWarning(f'superproject tag is not defined in manifest: '
f'{self._manifest.manifestFile}')
return SyncResult(False, False)
_PrintBetaNotice()
should_exit = True
if not self._remote_url:
self._LogWarning(f'superproject URL is not defined in manifest: '
f'{self._manifest.manifestFile}')
return SyncResult(False, should_exit)
if not self._Init():
return SyncResult(False, should_exit)
if not self._Fetch():
return SyncResult(False, should_exit)
if not self._quiet:
print('%s: Initial setup for superproject completed.' % self._work_git)
return SyncResult(True, False)
def _GetAllProjectsCommitIds(self):
"""Get commit ids for all projects from superproject and save them in _project_commit_ids.
Returns:
CommitIdsResult
"""
sync_result = self.Sync(self._git_event_log)
if not sync_result.success:
return CommitIdsResult(None, sync_result.fatal)
data = self._LsTree()
if not data:
self._LogWarning(f'git ls-tree failed to return data for manifest: '
f'{self._manifest.manifestFile}')
return CommitIdsResult(None, True)
# Parse lines like the following to select lines starting with '160000' and
# build a dictionary with project path (last element) and its commit id (3rd element).
#
# 160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00
# 120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00
commit_ids = {}
for line in data.split('\x00'):
ls_data = line.split(None, 3)
if not ls_data:
break
if ls_data[0] == '160000':
commit_ids[ls_data[3]] = ls_data[2]
self._project_commit_ids = commit_ids
return CommitIdsResult(commit_ids, False)
def _WriteManifestFile(self):
"""Writes manifest to a file.
Returns:
manifest_path: Path name of the file into which manifest is written instead of None.
"""
if not os.path.exists(self._superproject_path):
self._LogWarning(f'missing superproject directory: {self._superproject_path}')
return None
manifest_str = self._manifest.ToXml(groups=self._manifest.GetGroupsStr(),
omit_local=True).toxml()
manifest_path = self._manifest_path
try:
with open(manifest_path, 'w', encoding='utf-8') as fp:
fp.write(manifest_str)
except IOError as e:
self._LogError(f'cannot write manifest to : {manifest_path} {e}')
return None
return manifest_path
def _SkipUpdatingProjectRevisionId(self, project):
"""Checks if a project's revision id needs to be updated or not.
Revision id for projects from local manifest will not be updated.
Args:
project: project whose revision id is being updated.
Returns:
True if a project's revision id should not be updated, or False,
"""
path = project.relpath
if not path:
return True
# Skip the project with revisionId.
if project.revisionId:
return True
# Skip the project if it comes from the local manifest.
return project.manifest.IsFromLocalManifest(project)
def UpdateProjectsRevisionId(self, projects, git_event_log):
"""Update revisionId of every project in projects with the commit id.
Args:
projects: a list of projects whose revisionId needs to be updated.
git_event_log: an EventLog, for git tracing.
Returns:
UpdateProjectsResult
"""
self._git_event_log = git_event_log
commit_ids_result = self._GetAllProjectsCommitIds()
commit_ids = commit_ids_result.commit_ids
if not commit_ids:
return UpdateProjectsResult(None, commit_ids_result.fatal)
projects_missing_commit_ids = []
for project in projects:
if self._SkipUpdatingProjectRevisionId(project):
continue
path = project.relpath
commit_id = commit_ids.get(path)
if not commit_id:
projects_missing_commit_ids.append(path)
# If superproject doesn't have a commit id for a project, then report an
# error event and continue as if do not use superproject is specified.
if projects_missing_commit_ids:
self._LogWarning(f'please file a bug using {self._manifest.contactinfo.bugurl} '
f'to report missing commit_ids for: {projects_missing_commit_ids}')
return UpdateProjectsResult(None, False)
for project in projects:
if not self._SkipUpdatingProjectRevisionId(project):
project.SetRevisionId(commit_ids.get(project.relpath))
manifest_path = self._WriteManifestFile()
return UpdateProjectsResult(manifest_path, False)
@functools.lru_cache(maxsize=10)
def _PrintBetaNotice():
"""Print the notice of beta status."""
print('NOTICE: --use-superproject is in beta; report any issues to the '
'address described in `repo version`', file=sys.stderr)
@functools.lru_cache(maxsize=None)
def _UseSuperprojectFromConfiguration():
"""Returns the user choice of whether to use superproject."""
user_cfg = RepoConfig.ForUser()
time_now = int(time.time())
user_value = user_cfg.GetBoolean('repo.superprojectChoice')
if user_value is not None:
user_expiration = user_cfg.GetInt('repo.superprojectChoiceExpire')
if user_expiration is None or user_expiration <= 0 or user_expiration >= time_now:
# TODO(b/190688390) - Remove prompt when we are comfortable with the new
# default value.
if user_value:
print(('You are currently enrolled in Git submodules experiment '
'(go/android-submodules-quickstart). Use --no-use-superproject '
'to override.\n'), file=sys.stderr)
else:
print(('You are not currently enrolled in Git submodules experiment '
'(go/android-submodules-quickstart). Use --use-superproject '
'to override.\n'), file=sys.stderr)
return user_value
# We don't have an unexpired choice, ask for one.
system_cfg = RepoConfig.ForSystem()
system_value = system_cfg.GetBoolean('repo.superprojectChoice')
if system_value:
# The system configuration is proposing that we should enable the
# use of superproject. Treat the user as enrolled for two weeks.
#
# TODO(b/190688390) - Remove prompt when we are comfortable with the new
# default value.
userchoice = True
time_choiceexpire = time_now + (86400 * 14)
user_cfg.SetString('repo.superprojectChoiceExpire', str(time_choiceexpire))
user_cfg.SetBoolean('repo.superprojectChoice', userchoice)
print('You are automatically enrolled in Git submodules experiment '
'(go/android-submodules-quickstart) for another two weeks.\n',
file=sys.stderr)
return True
# For all other cases, we would not use superproject by default.
return False
def PrintMessages(use_superproject, manifest):
"""Returns a boolean if error/warning messages are to be printed.
Args:
use_superproject: option value from optparse.
manifest: manifest to use.
"""
return use_superproject is not None or bool(manifest.superproject)
def UseSuperproject(use_superproject, manifest):
"""Returns a boolean if use-superproject option is enabled.
Args:
use_superproject: option value from optparse.
manifest: manifest to use.
Returns:
Whether the superproject should be used.
"""
if not manifest.superproject:
# This (sub) manifest does not have a superproject definition.
return False
elif use_superproject is not None:
return use_superproject
else:
client_value = manifest.manifestProject.use_superproject
if client_value is not None:
return client_value
elif manifest.superproject:
return _UseSuperprojectFromConfiguration()
else:
return False

331
git_trace2_event_log.py Normal file
View File

@ -0,0 +1,331 @@
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Provide event logging in the git trace2 EVENT format.
The git trace2 EVENT format is defined at:
https://www.kernel.org/pub/software/scm/git/docs/technical/api-trace2.html#_event_format
https://git-scm.com/docs/api-trace2#_the_event_format_target
Usage:
git_trace_log = EventLog()
git_trace_log.StartEvent()
...
git_trace_log.ExitEvent()
git_trace_log.Write()
"""
import datetime
import errno
import json
import os
import socket
import sys
import tempfile
import threading
from git_command import GitCommand, RepoSourceVersion
class EventLog(object):
"""Event log that records events that occurred during a repo invocation.
Events are written to the log as a consecutive JSON entries, one per line.
Entries follow the git trace2 EVENT format.
Each entry contains the following common keys:
- event: The event name
- sid: session-id - Unique string to allow process instance to be identified.
- thread: The thread name.
- time: is the UTC time of the event.
Valid 'event' names and event specific fields are documented here:
https://git-scm.com/docs/api-trace2#_event_format
"""
def __init__(self, env=None):
"""Initializes the event log."""
self._log = []
# Try to get session-id (sid) from environment (setup in repo launcher).
KEY = 'GIT_TRACE2_PARENT_SID'
if env is None:
env = os.environ
now = datetime.datetime.utcnow()
# Save both our sid component and the complete sid.
# We use our sid component (self._sid) as the unique filename prefix and
# the full sid (self._full_sid) in the log itself.
self._sid = 'repo-%s-P%08x' % (now.strftime('%Y%m%dT%H%M%SZ'), os.getpid())
parent_sid = env.get(KEY)
# Append our sid component to the parent sid (if it exists).
if parent_sid is not None:
self._full_sid = parent_sid + '/' + self._sid
else:
self._full_sid = self._sid
# Set/update the environment variable.
# Environment handling across systems is messy.
try:
env[KEY] = self._full_sid
except UnicodeEncodeError:
env[KEY] = self._full_sid.encode()
# Add a version event to front of the log.
self._AddVersionEvent()
@property
def full_sid(self):
return self._full_sid
def _AddVersionEvent(self):
"""Adds a 'version' event at the beginning of current log."""
version_event = self._CreateEventDict('version')
version_event['evt'] = "2"
version_event['exe'] = RepoSourceVersion()
self._log.insert(0, version_event)
def _CreateEventDict(self, event_name):
"""Returns a dictionary with the common keys/values for git trace2 events.
Args:
event_name: The event name.
Returns:
Dictionary with the common event fields populated.
"""
return {
'event': event_name,
'sid': self._full_sid,
'thread': threading.currentThread().getName(),
'time': datetime.datetime.utcnow().isoformat() + 'Z',
}
def StartEvent(self):
"""Append a 'start' event to the current log."""
start_event = self._CreateEventDict('start')
start_event['argv'] = sys.argv
self._log.append(start_event)
def ExitEvent(self, result):
"""Append an 'exit' event to the current log.
Args:
result: Exit code of the event
"""
exit_event = self._CreateEventDict('exit')
# Consider 'None' success (consistent with event_log result handling).
if result is None:
result = 0
exit_event['code'] = result
self._log.append(exit_event)
def CommandEvent(self, name, subcommands):
"""Append a 'command' event to the current log.
Args:
name: Name of the primary command (ex: repo, git)
subcommands: List of the sub-commands (ex: version, init, sync)
"""
command_event = self._CreateEventDict('command')
command_event['name'] = name
command_event['subcommands'] = subcommands
self._log.append(command_event)
def LogConfigEvents(self, config, event_dict_name):
"""Append a |event_dict_name| event for each config key in |config|.
Args:
config: Configuration dictionary.
event_dict_name: Name of the event dictionary for items to be logged under.
"""
for param, value in config.items():
event = self._CreateEventDict(event_dict_name)
event['param'] = param
event['value'] = value
self._log.append(event)
def DefParamRepoEvents(self, config):
"""Append a 'def_param' event for each repo.* config key to the current log.
Args:
config: Repo configuration dictionary
"""
# Only output the repo.* config parameters.
repo_config = {k: v for k, v in config.items() if k.startswith('repo.')}
self.LogConfigEvents(repo_config, 'def_param')
def GetDataEventName(self, value):
"""Returns 'data-json' if the value is an array else returns 'data'."""
return 'data-json' if value[0] == '[' and value[-1] == ']' else 'data'
def LogDataConfigEvents(self, config, prefix):
"""Append a 'data' event for each config key/value in |config| to the current log.
For each keyX and valueX of the config, "key" field of the event is '|prefix|/keyX'
and the "value" of the "key" field is valueX.
Args:
config: Configuration dictionary.
prefix: Prefix for each key that is logged.
"""
for key, value in config.items():
event = self._CreateEventDict(self.GetDataEventName(value))
event['key'] = f'{prefix}/{key}'
event['value'] = value
self._log.append(event)
def ErrorEvent(self, msg, fmt):
"""Append a 'error' event to the current log."""
error_event = self._CreateEventDict('error')
error_event['msg'] = msg
error_event['fmt'] = fmt
self._log.append(error_event)
def _GetEventTargetPath(self):
"""Get the 'trace2.eventtarget' path from git configuration.
Returns:
path: git config's 'trace2.eventtarget' path if it exists, or None
"""
path = None
cmd = ['config', '--get', 'trace2.eventtarget']
# TODO(https://crbug.com/gerrit/13706): Use GitConfig when it supports
# system git config variables.
p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
bare=True)
retval = p.Wait()
if retval == 0:
# Strip trailing carriage-return in path.
path = p.stdout.rstrip('\n')
elif retval != 1:
# `git config --get` is documented to produce an exit status of `1` if
# the requested variable is not present in the configuration. Report any
# other return value as an error.
print("repo: error: 'git config --get' call failed with return code: %r, stderr: %r" % (
retval, p.stderr), file=sys.stderr)
return path
def _WriteLog(self, write_fn):
"""Writes the log out using a provided writer function.
Generate compact JSON output for each item in the log, and write it using
write_fn.
Args:
write_fn: A function that accepts byts and writes them to a destination.
"""
for e in self._log:
# Dump in compact encoding mode.
# See 'Compact encoding' in Python docs:
# https://docs.python.org/3/library/json.html#module-json
write_fn(json.dumps(e, indent=None, separators=(',', ':')).encode('utf-8') + b'\n')
def Write(self, path=None):
"""Writes the log out to a file or socket.
Log is only written if 'path' or 'git config --get trace2.eventtarget'
provide a valid path (or socket) to write logs to.
Logging filename format follows the git trace2 style of being a unique
(exclusive writable) file.
Args:
path: Path to where logs should be written. The path may have a prefix of
the form "af_unix:[{stream|dgram}:]", in which case the path is
treated as a Unix domain socket. See
https://git-scm.com/docs/api-trace2#_enabling_a_target for details.
Returns:
log_path: Path to the log file or socket if log is written, otherwise None
"""
log_path = None
# If no logging path is specified, get the path from 'trace2.eventtarget'.
if path is None:
path = self._GetEventTargetPath()
# If no logging path is specified, exit.
if path is None:
return None
path_is_socket = False
socket_type = None
if isinstance(path, str):
parts = path.split(':', 1)
if parts[0] == 'af_unix' and len(parts) == 2:
path_is_socket = True
path = parts[1]
parts = path.split(':', 1)
if parts[0] == 'stream' and len(parts) == 2:
socket_type = socket.SOCK_STREAM
path = parts[1]
elif parts[0] == 'dgram' and len(parts) == 2:
socket_type = socket.SOCK_DGRAM
path = parts[1]
else:
# Get absolute path.
path = os.path.abspath(os.path.expanduser(path))
else:
raise TypeError('path: str required but got %s.' % type(path))
# Git trace2 requires a directory to write log to.
# TODO(https://crbug.com/gerrit/13706): Support file (append) mode also.
if not (path_is_socket or os.path.isdir(path)):
return None
if path_is_socket:
if socket_type == socket.SOCK_STREAM or socket_type is None:
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.connect(path)
self._WriteLog(sock.sendall)
return f'af_unix:stream:{path}'
except OSError as err:
# If we tried to connect to a DGRAM socket using STREAM, ignore the
# attempt and continue to DGRAM below. Otherwise, issue a warning.
if err.errno != errno.EPROTOTYPE:
print(f'repo: warning: git trace2 logging failed: {err}', file=sys.stderr)
return None
if socket_type == socket.SOCK_DGRAM or socket_type is None:
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock:
self._WriteLog(lambda bs: sock.sendto(bs, path))
return f'af_unix:dgram:{path}'
except OSError as err:
print(f'repo: warning: git trace2 logging failed: {err}', file=sys.stderr)
return None
# Tried to open a socket but couldn't connect (SOCK_STREAM) or write
# (SOCK_DGRAM).
print('repo: warning: git trace2 logging failed: could not write to socket', file=sys.stderr)
return None
# Path is an absolute path
# Use NamedTemporaryFile to generate a unique filename as required by git trace2.
try:
with tempfile.NamedTemporaryFile(mode='xb', prefix=self._sid, dir=path,
delete=False) as f:
# TODO(https://crbug.com/gerrit/13706): Support writing events as they
# occur.
self._WriteLog(f.write)
log_path = f.name
except FileExistsError as err:
print('repo: warning: git trace2 logging failed: %r' % err,
file=sys.stderr)
return None
return log_path

156
gitc_utils.py Normal file
View File

@ -0,0 +1,156 @@
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import multiprocessing
import platform
import re
import sys
import time
import git_command
import git_config
import wrapper
from error import ManifestParseError
NUM_BATCH_RETRIEVE_REVISIONID = 32
def get_gitc_manifest_dir():
return wrapper.Wrapper().get_gitc_manifest_dir()
def parse_clientdir(gitc_fs_path):
return wrapper.Wrapper().gitc_parse_clientdir(gitc_fs_path)
def _get_project_revision(args):
"""Worker for _set_project_revisions to lookup one project remote."""
(i, url, expr) = args
gitcmd = git_command.GitCommand(
None, ['ls-remote', url, expr], capture_stdout=True, cwd='/tmp')
rc = gitcmd.Wait()
return (i, rc, gitcmd.stdout.split('\t', 1)[0])
def _set_project_revisions(projects):
"""Sets the revisionExpr for a list of projects.
Because of the limit of open file descriptors allowed, length of projects
should not be overly large. Recommend calling this function multiple times
with each call not exceeding NUM_BATCH_RETRIEVE_REVISIONID projects.
Args:
projects: List of project objects to set the revionExpr for.
"""
# Retrieve the commit id for each project based off of it's current
# revisionExpr and it is not already a commit id.
with multiprocessing.Pool(NUM_BATCH_RETRIEVE_REVISIONID) as pool:
results_iter = pool.imap_unordered(
_get_project_revision,
((i, project.remote.url, project.revisionExpr)
for i, project in enumerate(projects)
if not git_config.IsId(project.revisionExpr)),
chunksize=8)
for (i, rc, revisionExpr) in results_iter:
project = projects[i]
if rc:
print('FATAL: Failed to retrieve revisionExpr for %s' % project.name)
pool.terminate()
sys.exit(1)
if not revisionExpr:
pool.terminate()
raise ManifestParseError('Invalid SHA-1 revision project %s (%s)' %
(project.remote.url, project.revisionExpr))
project.revisionExpr = revisionExpr
def generate_gitc_manifest(gitc_manifest, manifest, paths=None):
"""Generate a manifest for shafsd to use for this GITC client.
Args:
gitc_manifest: Current gitc manifest, or None if there isn't one yet.
manifest: A GitcManifest object loaded with the current repo manifest.
paths: List of project paths we want to update.
"""
print('Generating GITC Manifest by fetching revision SHAs for each '
'project.')
if paths is None:
paths = list(manifest.paths.keys())
groups = [x for x in re.split(r'[,\s]+', manifest.GetGroupsStr()) if x]
# Convert the paths to projects, and filter them to the matched groups.
projects = [manifest.paths[p] for p in paths]
projects = [p for p in projects if p.MatchesGroups(groups)]
if gitc_manifest is not None:
for path, proj in manifest.paths.items():
if not proj.MatchesGroups(groups):
continue
if not proj.upstream and not git_config.IsId(proj.revisionExpr):
proj.upstream = proj.revisionExpr
if path not in gitc_manifest.paths:
# Any new projects need their first revision, even if we weren't asked
# for them.
projects.append(proj)
elif path not in paths:
# And copy revisions from the previous manifest if we're not updating
# them now.
gitc_proj = gitc_manifest.paths[path]
if gitc_proj.old_revision:
proj.revisionExpr = None
proj.old_revision = gitc_proj.old_revision
else:
proj.revisionExpr = gitc_proj.revisionExpr
_set_project_revisions(projects)
if gitc_manifest is not None:
for path, proj in gitc_manifest.paths.items():
if proj.old_revision and path in paths:
# If we updated a project that has been started, keep the old-revision
# updated.
repo_proj = manifest.paths[path]
repo_proj.old_revision = repo_proj.revisionExpr
repo_proj.revisionExpr = None
# Convert URLs from relative to absolute.
for _name, remote in manifest.remotes.items():
remote.fetchUrl = remote.resolvedFetchUrl
# Save the manifest.
save_manifest(manifest)
def save_manifest(manifest, client_dir=None):
"""Save the manifest file in the client_dir.
Args:
manifest: Manifest object to save.
client_dir: Client directory to save the manifest in.
"""
if not client_dir:
manifest_file = manifest.manifestFile
else:
manifest_file = os.path.join(client_dir, '.manifest')
with open(manifest_file, 'w') as f:
manifest.Save(f, groups=manifest.GetGroupsStr())
# TODO(sbasi/jorg): Come up with a solution to remove the sleep below.
# Give the GITC filesystem time to register the manifest changes.
time.sleep(3)

509
hooks.py Normal file
View File

@ -0,0 +1,509 @@
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
import json
import os
import re
import subprocess
import sys
import traceback
import urllib.parse
from error import HookError
from git_refs import HEAD
class RepoHook(object):
"""A RepoHook contains information about a script to run as a hook.
Hooks are used to run a python script before running an upload (for instance,
to run presubmit checks). Eventually, we may have hooks for other actions.
This shouldn't be confused with files in the 'repo/hooks' directory. Those
files are copied into each '.git/hooks' folder for each project. Repo-level
hooks are associated instead with repo actions.
Hooks are always python. When a hook is run, we will load the hook into the
interpreter and execute its main() function.
Combinations of hook option flags:
- no-verify=False, verify=False (DEFAULT):
If stdout is a tty, can prompt about running hooks if needed.
If user denies running hooks, the action is cancelled. If stdout is
not a tty and we would need to prompt about hooks, action is
cancelled.
- no-verify=False, verify=True:
Always run hooks with no prompt.
- no-verify=True, verify=False:
Never run hooks, but run action anyway (AKA bypass hooks).
- no-verify=True, verify=True:
Invalid
"""
def __init__(self,
hook_type,
hooks_project,
repo_topdir,
manifest_url,
bypass_hooks=False,
allow_all_hooks=False,
ignore_hooks=False,
abort_if_user_denies=False):
"""RepoHook constructor.
Params:
hook_type: A string representing the type of hook. This is also used
to figure out the name of the file containing the hook. For
example: 'pre-upload'.
hooks_project: The project containing the repo hooks.
If you have a manifest, this is manifest.repo_hooks_project.
OK if this is None, which will make the hook a no-op.
repo_topdir: The top directory of the repo client checkout.
This is the one containing the .repo directory. Scripts will
run with CWD as this directory.
If you have a manifest, this is manifest.topdir.
manifest_url: The URL to the manifest git repo.
bypass_hooks: If True, then 'Do not run the hook'.
allow_all_hooks: If True, then 'Run the hook without prompting'.
ignore_hooks: If True, then 'Do not abort action if hooks fail'.
abort_if_user_denies: If True, we'll abort running the hook if the user
doesn't allow us to run the hook.
"""
self._hook_type = hook_type
self._hooks_project = hooks_project
self._repo_topdir = repo_topdir
self._manifest_url = manifest_url
self._bypass_hooks = bypass_hooks
self._allow_all_hooks = allow_all_hooks
self._ignore_hooks = ignore_hooks
self._abort_if_user_denies = abort_if_user_denies
# Store the full path to the script for convenience.
if self._hooks_project:
self._script_fullpath = os.path.join(self._hooks_project.worktree,
self._hook_type + '.py')
else:
self._script_fullpath = None
def _GetHash(self):
"""Return a hash of the contents of the hooks directory.
We'll just use git to do this. This hash has the property that if anything
changes in the directory we will return a different has.
SECURITY CONSIDERATION:
This hash only represents the contents of files in the hook directory, not
any other files imported or called by hooks. Changes to imported files
can change the script behavior without affecting the hash.
Returns:
A string representing the hash. This will always be ASCII so that it can
be printed to the user easily.
"""
assert self._hooks_project, "Must have hooks to calculate their hash."
# We will use the work_git object rather than just calling GetRevisionId().
# That gives us a hash of the latest checked in version of the files that
# the user will actually be executing. Specifically, GetRevisionId()
# doesn't appear to change even if a user checks out a different version
# of the hooks repo (via git checkout) nor if a user commits their own revs.
#
# NOTE: Local (non-committed) changes will not be factored into this hash.
# I think this is OK, since we're really only worried about warning the user
# about upstream changes.
return self._hooks_project.work_git.rev_parse(HEAD)
def _GetMustVerb(self):
"""Return 'must' if the hook is required; 'should' if not."""
if self._abort_if_user_denies:
return 'must'
else:
return 'should'
def _CheckForHookApproval(self):
"""Check to see whether this hook has been approved.
We'll accept approval of manifest URLs if they're using secure transports.
This way the user can say they trust the manifest hoster. For insecure
hosts, we fall back to checking the hash of the hooks repo.
Note that we ask permission for each individual hook even though we use
the hash of all hooks when detecting changes. We'd like the user to be
able to approve / deny each hook individually. We only use the hash of all
hooks because there is no other easy way to detect changes to local imports.
Returns:
True if this hook is approved to run; False otherwise.
Raises:
HookError: Raised if the user doesn't approve and abort_if_user_denies
was passed to the consturctor.
"""
if self._ManifestUrlHasSecureScheme():
return self._CheckForHookApprovalManifest()
else:
return self._CheckForHookApprovalHash()
def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
changed_prompt):
"""Check for approval for a particular attribute and hook.
Args:
subkey: The git config key under [repo.hooks.<hook_type>] to store the
last approved string.
new_val: The new value to compare against the last approved one.
main_prompt: Message to display to the user to ask for approval.
changed_prompt: Message explaining why we're re-asking for approval.
Returns:
True if this hook is approved to run; False otherwise.
Raises:
HookError: Raised if the user doesn't approve and abort_if_user_denies
was passed to the consturctor.
"""
hooks_config = self._hooks_project.config
git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
# Get the last value that the user approved for this hook; may be None.
old_val = hooks_config.GetString(git_approval_key)
if old_val is not None:
# User previously approved hook and asked not to be prompted again.
if new_val == old_val:
# Approval matched. We're done.
return True
else:
# Give the user a reason why we're prompting, since they last told
# us to "never ask again".
prompt = 'WARNING: %s\n\n' % (changed_prompt,)
else:
prompt = ''
# Prompt the user if we're not on a tty; on a tty we'll assume "no".
if sys.stdout.isatty():
prompt += main_prompt + ' (yes/always/NO)? '
response = input(prompt).lower()
print()
# User is doing a one-time approval.
if response in ('y', 'yes'):
return True
elif response == 'always':
hooks_config.SetString(git_approval_key, new_val)
return True
# For anything else, we'll assume no approval.
if self._abort_if_user_denies:
raise HookError('You must allow the %s hook or use --no-verify.' %
self._hook_type)
return False
def _ManifestUrlHasSecureScheme(self):
"""Check if the URI for the manifest is a secure transport."""
secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
parse_results = urllib.parse.urlparse(self._manifest_url)
return parse_results.scheme in secure_schemes
def _CheckForHookApprovalManifest(self):
"""Check whether the user has approved this manifest host.
Returns:
True if this hook is approved to run; False otherwise.
"""
return self._CheckForHookApprovalHelper(
'approvedmanifest',
self._manifest_url,
'Run hook scripts from %s' % (self._manifest_url,),
'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
def _CheckForHookApprovalHash(self):
"""Check whether the user has approved the hooks repo.
Returns:
True if this hook is approved to run; False otherwise.
"""
prompt = ('Repo %s run the script:\n'
' %s\n'
'\n'
'Do you want to allow this script to run')
return self._CheckForHookApprovalHelper(
'approvedhash',
self._GetHash(),
prompt % (self._GetMustVerb(), self._script_fullpath),
'Scripts have changed since %s was allowed.' % (self._hook_type,))
@staticmethod
def _ExtractInterpFromShebang(data):
"""Extract the interpreter used in the shebang.
Try to locate the interpreter the script is using (ignoring `env`).
Args:
data: The file content of the script.
Returns:
The basename of the main script interpreter, or None if a shebang is not
used or could not be parsed out.
"""
firstline = data.splitlines()[:1]
if not firstline:
return None
# The format here can be tricky.
shebang = firstline[0].strip()
m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
if not m:
return None
# If the using `env`, find the target program.
interp = m.group(1)
if os.path.basename(interp) == 'env':
interp = m.group(2)
return interp
def _ExecuteHookViaReexec(self, interp, context, **kwargs):
"""Execute the hook script through |interp|.
Note: Support for this feature should be dropped ~Jun 2021.
Args:
interp: The Python program to run.
context: Basic Python context to execute the hook inside.
kwargs: Arbitrary arguments to pass to the hook script.
Raises:
HookError: When the hooks failed for any reason.
"""
# This logic needs to be kept in sync with _ExecuteHookViaImport below.
script = """
import json, os, sys
path = '''%(path)s'''
kwargs = json.loads('''%(kwargs)s''')
context = json.loads('''%(context)s''')
sys.path.insert(0, os.path.dirname(path))
data = open(path).read()
exec(compile(data, path, 'exec'), context)
context['main'](**kwargs)
""" % {
'path': self._script_fullpath,
'kwargs': json.dumps(kwargs),
'context': json.dumps(context),
}
# We pass the script via stdin to avoid OS argv limits. It also makes
# unhandled exception tracebacks less verbose/confusing for users.
cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.communicate(input=script.encode('utf-8'))
if proc.returncode:
raise HookError('Failed to run %s hook.' % (self._hook_type,))
def _ExecuteHookViaImport(self, data, context, **kwargs):
"""Execute the hook code in |data| directly.
Args:
data: The code of the hook to execute.
context: Basic Python context to execute the hook inside.
kwargs: Arbitrary arguments to pass to the hook script.
Raises:
HookError: When the hooks failed for any reason.
"""
# Exec, storing global context in the context dict. We catch exceptions
# and convert to a HookError w/ just the failing traceback.
try:
exec(compile(data, self._script_fullpath, 'exec'), context)
except Exception:
raise HookError('%s\nFailed to import %s hook; see traceback above.' %
(traceback.format_exc(), self._hook_type))
# Running the script should have defined a main() function.
if 'main' not in context:
raise HookError('Missing main() in: "%s"' % self._script_fullpath)
# Call the main function in the hook. If the hook should cause the
# build to fail, it will raise an Exception. We'll catch that convert
# to a HookError w/ just the failing traceback.
try:
context['main'](**kwargs)
except Exception:
raise HookError('%s\nFailed to run main() for %s hook; see traceback '
'above.' % (traceback.format_exc(), self._hook_type))
def _ExecuteHook(self, **kwargs):
"""Actually execute the given hook.
This will run the hook's 'main' function in our python interpreter.
Args:
kwargs: Keyword arguments to pass to the hook. These are often specific
to the hook type. For instance, pre-upload hooks will contain
a project_list.
"""
# Keep sys.path and CWD stashed away so that we can always restore them
# upon function exit.
orig_path = os.getcwd()
orig_syspath = sys.path
try:
# Always run hooks with CWD as topdir.
os.chdir(self._repo_topdir)
# Put the hook dir as the first item of sys.path so hooks can do
# relative imports. We want to replace the repo dir as [0] so
# hooks can't import repo files.
sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
# Initial global context for the hook to run within.
context = {'__file__': self._script_fullpath}
# Add 'hook_should_take_kwargs' to the arguments to be passed to main.
# We don't actually want hooks to define their main with this argument--
# it's there to remind them that their hook should always take **kwargs.
# For instance, a pre-upload hook should be defined like:
# def main(project_list, **kwargs):
#
# This allows us to later expand the API without breaking old hooks.
kwargs = kwargs.copy()
kwargs['hook_should_take_kwargs'] = True
# See what version of python the hook has been written against.
data = open(self._script_fullpath).read()
interp = self._ExtractInterpFromShebang(data)
reexec = False
if interp:
prog = os.path.basename(interp)
if prog.startswith('python2') and sys.version_info.major != 2:
reexec = True
elif prog.startswith('python3') and sys.version_info.major == 2:
reexec = True
# Attempt to execute the hooks through the requested version of Python.
if reexec:
try:
self._ExecuteHookViaReexec(interp, context, **kwargs)
except OSError as e:
if e.errno == errno.ENOENT:
# We couldn't find the interpreter, so fallback to importing.
reexec = False
else:
raise
# Run the hook by importing directly.
if not reexec:
self._ExecuteHookViaImport(data, context, **kwargs)
finally:
# Restore sys.path and CWD.
sys.path = orig_syspath
os.chdir(orig_path)
def _CheckHook(self):
# Bail with a nice error if we can't find the hook.
if not os.path.isfile(self._script_fullpath):
raise HookError('Couldn\'t find repo hook: %s' % self._script_fullpath)
def Run(self, **kwargs):
"""Run the hook.
If the hook doesn't exist (because there is no hooks project or because
this particular hook is not enabled), this is a no-op.
Args:
user_allows_all_hooks: If True, we will never prompt about running the
hook--we'll just assume it's OK to run it.
kwargs: Keyword arguments to pass to the hook. These are often specific
to the hook type. For instance, pre-upload hooks will contain
a project_list.
Returns:
True: On success or ignore hooks by user-request
False: The hook failed. The caller should respond with aborting the action.
Some examples in which False is returned:
* Finding the hook failed while it was enabled, or
* the user declined to run a required hook (from _CheckForHookApproval)
In all these cases the user did not pass the proper arguments to
ignore the result through the option combinations as listed in
AddHookOptionGroup().
"""
# Do not do anything in case bypass_hooks is set, or
# no-op if there is no hooks project or if hook is disabled.
if (self._bypass_hooks or
not self._hooks_project or
self._hook_type not in self._hooks_project.enabled_repo_hooks):
return True
passed = True
try:
self._CheckHook()
# Make sure the user is OK with running the hook.
if self._allow_all_hooks or self._CheckForHookApproval():
# Run the hook with the same version of python we're using.
self._ExecuteHook(**kwargs)
except SystemExit as e:
passed = False
print('ERROR: %s hooks exited with exit code: %s' % (self._hook_type, str(e)),
file=sys.stderr)
except HookError as e:
passed = False
print('ERROR: %s' % str(e), file=sys.stderr)
if not passed and self._ignore_hooks:
print('\nWARNING: %s hooks failed, but continuing anyways.' % self._hook_type,
file=sys.stderr)
passed = True
return passed
@classmethod
def FromSubcmd(cls, manifest, opt, *args, **kwargs):
"""Method to construct the repo hook class
Args:
manifest: The current active manifest for this command from which we
extract a couple of fields.
opt: Contains the commandline options for the action of this hook.
It should contain the options added by AddHookOptionGroup() in which
we are interested in RepoHook execution.
"""
for key in ('bypass_hooks', 'allow_all_hooks', 'ignore_hooks'):
kwargs.setdefault(key, getattr(opt, key))
kwargs.update({
'hooks_project': manifest.repo_hooks_project,
'repo_topdir': manifest.topdir,
'manifest_url': manifest.manifestProject.GetRemote('origin').url,
})
return cls(*args, **kwargs)
@staticmethod
def AddOptionGroup(parser, name):
"""Help options relating to the various hooks."""
# Note that verify and no-verify are NOT opposites of each other, which
# is why they store to different locations. We are using them to match
# 'git commit' syntax.
group = parser.add_option_group(name + ' hooks')
group.add_option('--no-verify',
dest='bypass_hooks', action='store_true',
help='Do not run the %s hook.' % name)
group.add_option('--verify',
dest='allow_all_hooks', action='store_true',
help='Run the %s hook without prompting.' % name)
group.add_option('--ignore-hooks',
action='store_true',
help='Do not abort if %s hooks fail.' % name)

View File

@ -1,7 +1,7 @@
#!/bin/sh
# From Gerrit Code Review 2.6
# From Gerrit Code Review 3.6.1 c67916dbdc07555c44e32a68f92ffc484b9b34f0
#
# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
#
# Copyright (C) 2009 The Android Open Source Project
#
@ -16,161 +16,89 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
unset GREP_OPTIONS
set -u
CHANGE_ID_AFTER="Bug|Issue"
MSG="$1"
# avoid [[ which is not POSIX sh.
if test "$#" != 1 ; then
echo "$0 requires an argument."
exit 1
fi
# Check for, and add if missing, a unique Change-Id
#
add_ChangeId() {
clean_message=`sed -e '
/^diff --git a\/.*/{
s///
q
}
/^Signed-off-by:/d
/^#/d
' "$MSG" | git stripspace`
if test -z "$clean_message"
then
return
fi
if test ! -f "$1" ; then
echo "file does not exist: $1"
exit 1
fi
# Does Change-Id: already exist? if so, exit (no change).
if grep -i '^Change-Id:' "$MSG" >/dev/null
then
return
fi
# Do not create a change id if requested
if test "false" = "$(git config --bool --get gerrit.createChangeId)" ; then
exit 0
fi
id=`_gen_ChangeId`
T="$MSG.tmp.$$"
AWK=awk
if [ -x /usr/xpg4/bin/awk ]; then
# Solaris AWK is just too broken
AWK=/usr/xpg4/bin/awk
fi
# Do not create a change id for squash commits.
if head -n1 "$1" | grep -q '^squash! '; then
exit 0
fi
# How this works:
# - parse the commit message as (textLine+ blankLine*)*
# - assume textLine+ to be a footer until proven otherwise
# - exception: the first block is not footer (as it is the title)
# - read textLine+ into a variable
# - then count blankLines
# - once the next textLine appears, print textLine+ blankLine* as these
# aren't footer
# - in END, the last textLine+ block is available for footer parsing
$AWK '
BEGIN {
# while we start with the assumption that textLine+
# is a footer, the first block is not.
isFooter = 0
footerComment = 0
blankLines = 0
}
if git rev-parse --verify HEAD >/dev/null 2>&1; then
refhash="$(git rev-parse HEAD)"
else
refhash="$(git hash-object -t tree /dev/null)"
fi
# Skip lines starting with "#" without any spaces before it.
/^#/ { next }
random=$({ git var GIT_COMMITTER_IDENT ; echo "$refhash" ; cat "$1"; } | git hash-object --stdin)
dest="$1.tmp.${random}"
# Skip the line starting with the diff command and everything after it,
# up to the end of the file, assuming it is only patch data.
# If more than one line before the diff was empty, strip all but one.
/^diff --git a/ {
blankLines = 0
while (getline) { }
next
}
trap 'rm -f "$dest" "$dest-2"' EXIT
# Count blank lines outside footer comments
/^$/ && (footerComment == 0) {
blankLines++
next
}
if ! git stripspace --strip-comments < "$1" > "${dest}" ; then
echo "cannot strip comments from $1"
exit 1
fi
# Catch footer comment
/^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
footerComment = 1
}
if test ! -s "${dest}" ; then
echo "file is empty: $1"
exit 1
fi
/]$/ && (footerComment == 1) {
footerComment = 2
}
reviewurl="$(git config --get gerrit.reviewUrl)"
if test -n "${reviewurl}" ; then
token="Link"
value="${reviewurl%/}/id/I$random"
pattern=".*/id/I[0-9a-f]\{40\}$"
else
token="Change-Id"
value="I$random"
pattern=".*"
fi
# We have a non-blank line after blank lines. Handle this.
(blankLines > 0) {
print lines
for (i = 0; i < blankLines; i++) {
print ""
}
if git interpret-trailers --parse < "$1" | grep -q "^$token: $pattern$" ; then
exit 0
fi
lines = ""
blankLines = 0
isFooter = 1
footerComment = 0
}
# There must be a Signed-off-by trailer for the code below to work. Insert a
# sentinel at the end to make sure there is one.
# Avoid the --in-place option which only appeared in Git 2.8
if ! git interpret-trailers \
--trailer "Signed-off-by: SENTINEL" < "$1" > "$dest-2" ; then
echo "cannot insert Signed-off-by sentinel line in $1"
exit 1
fi
# Detect that the current block is not the footer
(footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
isFooter = 0
}
# Make sure the trailer appears before any Signed-off-by trailers by inserting
# it as if it was a Signed-off-by trailer and then use sed to remove the
# Signed-off-by prefix and the Signed-off-by sentinel line.
# Avoid the --in-place option which only appeared in Git 2.8
# Avoid the --where option which only appeared in Git 2.15
if ! git -c trailer.where=before interpret-trailers \
--trailer "Signed-off-by: $token: $value" < "$dest-2" |
sed -re "s/^Signed-off-by: ($token: )/\1/" \
-e "/^Signed-off-by: SENTINEL/d" > "$dest" ; then
echo "cannot insert $token line in $1"
exit 1
fi
{
# We need this information about the current last comment line
if (footerComment == 2) {
footerComment = 0
}
if (lines != "") {
lines = lines "\n";
}
lines = lines $0
}
# Footer handling:
# If the last block is considered a footer, splice in the Change-Id at the
# right place.
# Look for the right place to inject Change-Id by considering
# CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
# then Change-Id, then everything else (eg. Signed-off-by:).
#
# Otherwise just print the last block, a new line and the Change-Id as a
# block of its own.
END {
unprinted = 1
if (isFooter == 0) {
print lines "\n"
lines = ""
}
changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
numlines = split(lines, footer, "\n")
for (line = 1; line <= numlines; line++) {
if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
unprinted = 0
print "Change-Id: I'"$id"'"
}
print footer[line]
}
if (unprinted) {
print "Change-Id: I'"$id"'"
}
}' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
}
_gen_ChangeIdInput() {
echo "tree `git write-tree`"
if parent=`git rev-parse "HEAD^0" 2>/dev/null`
then
echo "parent $parent"
fi
echo "author `git var GIT_AUTHOR_IDENT`"
echo "committer `git var GIT_COMMITTER_IDENT`"
echo
printf '%s' "$clean_message"
}
_gen_ChangeId() {
_gen_ChangeIdInput |
git hash-object -t commit --stdin
}
add_ChangeId
if ! mv "${dest}" "$1" ; then
echo "cannot mv ${dest} to $1"
exit 1
fi

View File

@ -1,9 +1,9 @@
#!/bin/sh
#
# An example hook script to verify if you are on battery, in case you
# are running Linux or OS X. Called by git-gc --auto with no arguments.
# The hook should exit with non-zero status after issuing an appropriate
# message if it wants to stop the auto repacking.
# are running Windows, Linux or OS X. Called by git-gc --auto with no
# arguments. The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the auto repacking.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,7 +19,17 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
if test -x /sbin/on_ac_power && /sbin/on_ac_power
if uname -s | grep -q "_NT-"
then
if test -x $SYSTEMROOT/System32/Wbem/wmic
then
STATUS=$(wmic path win32_battery get batterystatus /format:list | tr -d '\r\n')
[ "$STATUS" = "BatteryStatus=2" ] && exit 0 || exit 1
fi
exit 0
fi
if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1)
then
exit 0
elif test "$(cat /sys/class/power_supply/AC/online 2>/dev/null)" = 1

437
main.py
View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
#
# Copyright (C) 2008 The Android Open Source Project
#
@ -14,133 +14,266 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
"""The repo tool.
People shouldn't run this directly; instead, they should use the `repo` wrapper
which takes care of execing this entry point.
"""
import getpass
import imp
import netrc
import optparse
import os
import shlex
import sys
import textwrap
import time
from pyversion import is_python3
if is_python3():
import urllib.request
else:
import urllib2
urllib = imp.new_module('urllib')
urllib.request = urllib2
import urllib.request
try:
import kerberos
except ImportError:
kerberos = None
from trace import SetTrace
from git_command import git, GitCommand
from git_config import init_ssh, close_ssh
from color import SetDefaultColoring
import event_log
from repo_trace import SetTrace
from git_command import user_agent
from git_config import RepoConfig
from git_trace2_event_log import EventLog
from command import InteractiveCommand
from command import MirrorSafeCommand
from command import GitcAvailableCommand, GitcClientCommand
from subcmds.version import Version
from editor import Editor
from error import DownloadError
from error import InvalidProjectGroupsError
from error import ManifestInvalidRevisionError
from error import ManifestParseError
from error import NoManifestException
from error import NoSuchProjectError
from error import RepoChangedException
from manifest_xml import XmlManifest
from pager import RunPager
import gitc_utils
from manifest_xml import GitcClient, RepoClient
from pager import RunPager, TerminatePager
from wrapper import WrapperPath, Wrapper
from subcmds import all_commands
if not is_python3():
# pylint:disable=W0622
input = raw_input
# pylint:enable=W0622
# NB: These do not need to be kept in sync with the repo launcher script.
# These may be much newer as it allows the repo launcher to roll between
# different repo releases while source versions might require a newer python.
#
# The soft version is when we start warning users that the version is old and
# we'll be dropping support for it. We'll refuse to work with versions older
# than the hard version.
#
# python-3.6 is in Ubuntu Bionic.
MIN_PYTHON_VERSION_SOFT = (3, 6)
MIN_PYTHON_VERSION_HARD = (3, 6)
if sys.version_info.major < 3:
print('repo: error: Python 2 is no longer supported; '
'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION_SOFT),
file=sys.stderr)
sys.exit(1)
else:
if sys.version_info < MIN_PYTHON_VERSION_HARD:
print('repo: error: Python 3 version is too old; '
'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION_SOFT),
file=sys.stderr)
sys.exit(1)
elif sys.version_info < MIN_PYTHON_VERSION_SOFT:
print('repo: warning: your Python 3 version is no longer supported; '
'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION_SOFT),
file=sys.stderr)
global_options = optparse.OptionParser(
usage="repo [-p|--paginate|--no-pager] COMMAND [ARGS]"
)
usage='repo [-p|--paginate|--no-pager] COMMAND [ARGS]',
add_help_option=False)
global_options.add_option('-h', '--help', action='store_true',
help='show this help message and exit')
global_options.add_option('--help-all', action='store_true',
help='show this help message with all subcommands and exit')
global_options.add_option('-p', '--paginate',
dest='pager', action='store_true',
help='display command output in the pager')
global_options.add_option('--no-pager',
dest='no_pager', action='store_true',
dest='pager', action='store_false',
help='disable the pager')
global_options.add_option('--color',
choices=('auto', 'always', 'never'), default=None,
help='control color usage: auto, always, never')
global_options.add_option('--trace',
dest='trace', action='store_true',
help='trace git command execution')
help='trace git command execution (REPO_TRACE=1)')
global_options.add_option('--trace-python',
dest='trace_python', action='store_true',
help='trace python command execution')
global_options.add_option('--time',
dest='time', action='store_true',
help='time repo command execution')
global_options.add_option('--version',
dest='show_version', action='store_true',
help='display this version of repo')
global_options.add_option('--show-toplevel',
action='store_true',
help='display the path of the top-level directory of '
'the repo client checkout')
global_options.add_option('--event-log',
dest='event_log', action='store',
help='filename of event log to append timeline to')
global_options.add_option('--git-trace2-event-log', action='store',
help='directory to write git trace2 event log to')
global_options.add_option('--submanifest-path', action='store',
metavar='REL_PATH', help='submanifest path')
class _Repo(object):
def __init__(self, repodir):
self.repodir = repodir
self.commands = all_commands
# add 'branch' as an alias for 'branches'
all_commands['branch'] = all_commands['branches']
def _Run(self, argv):
result = 0
name = None
glob = []
def _PrintHelp(self, short: bool = False, all_commands: bool = False):
"""Show --help screen."""
global_options.print_help()
print()
if short:
commands = ' '.join(sorted(self.commands))
wrapped_commands = textwrap.wrap(commands, width=77)
print('Available commands:\n %s' % ('\n '.join(wrapped_commands),))
print('\nRun `repo help <command>` for command-specific details.')
print('Bug reports:', Wrapper().BUG_URL)
else:
cmd = self.commands['help']()
if all_commands:
cmd.PrintAllCommandsBody()
else:
cmd.PrintCommonCommandsBody()
for i in range(len(argv)):
if not argv[i].startswith('-'):
name = argv[i]
if i > 0:
glob = argv[:i]
def _ParseArgs(self, argv):
"""Parse the main `repo` command line options."""
for i, arg in enumerate(argv):
if not arg.startswith('-'):
name = arg
glob = argv[:i]
argv = argv[i + 1:]
break
if not name:
else:
name = None
glob = argv
name = 'help'
argv = []
gopts, _gargs = global_options.parse_args(glob)
if name:
name, alias_args = self._ExpandAlias(name)
argv = alias_args + argv
return (name, gopts, argv)
def _ExpandAlias(self, name):
"""Look up user registered aliases."""
# We don't resolve aliases for existing subcommands. This matches git.
if name in self.commands:
return name, []
key = 'alias.%s' % (name,)
alias = RepoConfig.ForRepository(self.repodir).GetString(key)
if alias is None:
alias = RepoConfig.ForUser().GetString(key)
if alias is None:
return name, []
args = alias.strip().split(' ', 1)
name = args[0]
if len(args) == 2:
args = shlex.split(args[1])
else:
args = []
return name, args
def _Run(self, name, gopts, argv):
"""Execute the requested subcommand."""
result = 0
if gopts.trace:
SetTrace()
if gopts.show_version:
if name == 'help':
name = 'version'
else:
print('fatal: invalid usage of --version', file=sys.stderr)
return 1
# Handle options that terminate quickly first.
if gopts.help or gopts.help_all:
self._PrintHelp(short=False, all_commands=gopts.help_all)
return 0
elif gopts.show_version:
# Always allow global --version regardless of subcommand validity.
name = 'version'
elif gopts.show_toplevel:
print(os.path.dirname(self.repodir))
return 0
elif not name:
# No subcommand specified, so show the help/subcommand.
self._PrintHelp(short=True)
return 1
SetDefaultColoring(gopts.color)
git_trace2_event_log = EventLog()
outer_client = RepoClient(self.repodir)
repo_client = outer_client
if gopts.submanifest_path:
repo_client = RepoClient(self.repodir,
submanifest_path=gopts.submanifest_path,
outer_client=outer_client)
gitc_manifest = None
gitc_client_name = gitc_utils.parse_clientdir(os.getcwd())
if gitc_client_name:
gitc_manifest = GitcClient(self.repodir, gitc_client_name)
repo_client.isGitcClient = True
try:
cmd = self.commands[name]
cmd = self.commands[name](
repodir=self.repodir,
client=repo_client,
manifest=repo_client.manifest,
outer_client=outer_client,
outer_manifest=outer_client.manifest,
gitc_manifest=gitc_manifest,
git_event_log=git_trace2_event_log)
except KeyError:
print("repo: '%s' is not a repo command. See 'repo help'." % name,
file=sys.stderr)
return 1
cmd.repodir = self.repodir
cmd.manifest = XmlManifest(cmd.repodir)
Editor.globalConfig = cmd.manifest.globalConfig
Editor.globalConfig = cmd.client.globalConfig
if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
print("fatal: '%s' requires a working directory" % name,
file=sys.stderr)
return 1
if isinstance(cmd, GitcAvailableCommand) and not gitc_utils.get_gitc_manifest_dir():
print("fatal: '%s' requires GITC to be available" % name,
file=sys.stderr)
return 1
if isinstance(cmd, GitcClientCommand) and not gitc_client_name:
print("fatal: '%s' requires a GITC client" % name,
file=sys.stderr)
return 1
try:
copts, cargs = cmd.OptionParser.parse_args(argv)
copts = cmd.ReadEnvironmentOptions(copts)
except NoManifestException as e:
print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
file=sys.stderr)
file=sys.stderr)
print('error: manifest missing or unreadable -- please run init',
file=sys.stderr)
return 1
if not gopts.no_pager and not isinstance(cmd, InteractiveCommand):
config = cmd.manifest.globalConfig
if gopts.pager is not False and not isinstance(cmd, InteractiveCommand):
config = cmd.client.globalConfig
if gopts.pager:
use_pager = True
else:
@ -151,12 +284,48 @@ class _Repo(object):
RunPager(config)
start = time.time()
cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start)
cmd.event_log.SetParent(cmd_event)
git_trace2_event_log.StartEvent()
git_trace2_event_log.CommandEvent(name='repo', subcommands=[name])
try:
result = cmd.Execute(copts, cargs)
cmd.CommonValidateOptions(copts, cargs)
cmd.ValidateOptions(copts, cargs)
this_manifest_only = copts.this_manifest_only
outer_manifest = copts.outer_manifest
if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only:
result = cmd.Execute(copts, cargs)
elif outer_manifest and repo_client.manifest.is_submanifest:
# The command does not support multi-manifest, we are using a
# submanifest, and the command line is for the outermost manifest.
# Re-run using the outermost manifest, which will recurse through the
# submanifests.
gopts.submanifest_path = ''
result = self._Run(name, gopts, argv)
else:
# No multi-manifest support. Run the command in the current
# (sub)manifest, and then any child submanifests.
result = cmd.Execute(copts, cargs)
for submanifest in repo_client.manifest.submanifests.values():
spec = submanifest.ToSubmanifestSpec()
gopts.submanifest_path = submanifest.repo_client.path_prefix
child_argv = argv[:]
child_argv.append('--no-outer-manifest')
# Not all subcommands support the 3 manifest options, so only add them
# if the original command includes them.
if hasattr(copts, 'manifest_url'):
child_argv.extend(['--manifest-url', spec.manifestUrl])
if hasattr(copts, 'manifest_name'):
child_argv.extend(['--manifest-name', spec.manifestName])
if hasattr(copts, 'manifest_branch'):
child_argv.extend(['--manifest-branch', spec.revision])
result = self._Run(name, gopts, child_argv) or result
except (DownloadError, ManifestInvalidRevisionError,
NoManifestException) as e:
NoManifestException) as e:
print('error: in `%s`: %s' % (' '.join([name] + argv), str(e)),
file=sys.stderr)
file=sys.stderr)
if isinstance(e, NoManifestException):
print('error: manifest missing or unreadable -- please run init',
file=sys.stderr)
@ -167,8 +336,20 @@ class _Repo(object):
else:
print('error: no project in current directory', file=sys.stderr)
result = 1
except InvalidProjectGroupsError as e:
if e.name:
print('error: project group must be enabled for project %s' % e.name, file=sys.stderr)
else:
print('error: project group must be enabled for the project in the current directory',
file=sys.stderr)
result = 1
except SystemExit as e:
if e.code:
result = e.code
raise
finally:
elapsed = time.time() - start
finish = time.time()
elapsed = finish - start
hours, remainder = divmod(elapsed, 3600)
minutes, seconds = divmod(remainder, 60)
if gopts.time:
@ -178,49 +359,80 @@ class _Repo(object):
print('real\t%dh%dm%.3fs' % (hours, minutes, seconds),
file=sys.stderr)
cmd.event_log.FinishEvent(cmd_event, finish,
result is None or result == 0)
git_trace2_event_log.DefParamRepoEvents(
cmd.manifest.manifestProject.config.DumpConfigDict())
git_trace2_event_log.ExitEvent(result)
if gopts.event_log:
cmd.event_log.Write(os.path.abspath(
os.path.expanduser(gopts.event_log)))
git_trace2_event_log.Write(gopts.git_trace2_event_log)
return result
def _MyRepoPath():
return os.path.dirname(__file__)
def _CheckWrapperVersion(ver_str, repo_path):
"""Verify the repo launcher is new enough for this checkout.
Args:
ver_str: The version string passed from the repo launcher when it ran us.
repo_path: The path to the repo launcher that loaded us.
"""
# Refuse to work with really old wrapper versions. We don't test these,
# so might as well require a somewhat recent sane version.
# v1.15 of the repo launcher was released in ~Mar 2012.
MIN_REPO_VERSION = (1, 15)
min_str = '.'.join(str(x) for x in MIN_REPO_VERSION)
def _CheckWrapperVersion(ver, repo_path):
if not repo_path:
repo_path = '~/bin/repo'
if not ver:
if not ver_str:
print('no --wrapper-version argument', file=sys.stderr)
sys.exit(1)
# Pull out the version of the repo launcher we know about to compare.
exp = Wrapper().VERSION
ver = tuple(map(int, ver.split('.')))
if len(ver) == 1:
ver = (0, ver[0])
ver = tuple(map(int, ver_str.split('.')))
exp_str = '.'.join(map(str, exp))
if exp[0] > ver[0] or ver < (0, 4):
if ver < MIN_REPO_VERSION:
print("""
!!! A new repo command (%5s) is available. !!!
!!! You must upgrade before you can continue: !!!
repo: error:
!!! Your version of repo %s is too old.
!!! We need at least version %s.
!!! A new version of repo (%s) is available.
!!! You must upgrade before you can continue:
cp %s %s
""" % (exp_str, WrapperPath(), repo_path), file=sys.stderr)
""" % (ver_str, min_str, exp_str, WrapperPath(), repo_path), file=sys.stderr)
sys.exit(1)
if exp > ver:
print("""
... A new repo command (%5s) is available.
print('\n... A new version of repo (%s) is available.' % (exp_str,),
file=sys.stderr)
if os.access(repo_path, os.W_OK):
print("""\
... You should upgrade soon:
cp %s %s
""" % (exp_str, WrapperPath(), repo_path), file=sys.stderr)
""" % (WrapperPath(), repo_path), file=sys.stderr)
else:
print("""\
... New version is available at: %s
... The launcher is run from: %s
!!! The launcher is not writable. Please talk to your sysadmin or distro
!!! to get an update installed.
""" % (WrapperPath(), repo_path), file=sys.stderr)
def _CheckRepoDir(repo_dir):
if not repo_dir:
print('no --repo-dir argument', file=sys.stderr)
sys.exit(1)
def _PruneOptions(argv, opt):
i = 0
while i < len(argv):
@ -236,53 +448,17 @@ def _PruneOptions(argv, opt):
continue
i += 1
_user_agent = None
def _UserAgent():
global _user_agent
if _user_agent is None:
py_version = sys.version_info
os_name = sys.platform
if os_name == 'linux2':
os_name = 'Linux'
elif os_name == 'win32':
os_name = 'Win32'
elif os_name == 'cygwin':
os_name = 'Cygwin'
elif os_name == 'darwin':
os_name = 'Darwin'
p = GitCommand(
None, ['describe', 'HEAD'],
cwd = _MyRepoPath(),
capture_stdout = True)
if p.Wait() == 0:
repo_version = p.stdout
if len(repo_version) > 0 and repo_version[-1] == '\n':
repo_version = repo_version[0:-1]
if len(repo_version) > 0 and repo_version[0] == 'v':
repo_version = repo_version[1:]
else:
repo_version = 'unknown'
_user_agent = 'git-repo/%s (%s) git/%s Python/%d.%d.%d' % (
repo_version,
os_name,
'.'.join(map(str, git.version_tuple())),
py_version[0], py_version[1], py_version[2])
return _user_agent
class _UserAgentHandler(urllib.request.BaseHandler):
def http_request(self, req):
req.add_header('User-Agent', _UserAgent())
req.add_header('User-Agent', user_agent.repo)
return req
def https_request(self, req):
req.add_header('User-Agent', _UserAgent())
req.add_header('User-Agent', user_agent.repo)
return req
def _AddPasswordFromUserInput(handler, msg, req):
# If repo could not find auth info from netrc, try to get it from user input
url = req.get_full_url()
@ -296,22 +472,24 @@ def _AddPasswordFromUserInput(handler, msg, req):
return
handler.passwd.add_password(None, url, user, password)
class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req)
return urllib.request.HTTPBasicAuthHandler.http_error_401(
self, req, fp, code, msg, headers)
self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, authreq, host, req, headers):
try:
old_add_header = req.add_header
def _add_header(name, val):
val = val.replace('\n', '')
old_add_header(name, val)
req.add_header = _add_header
return urllib.request.AbstractBasicAuthHandler.http_error_auth_reqed(
self, authreq, host, req, headers)
except:
self, authreq, host, req, headers)
except Exception:
reset = getattr(self, 'reset_retry_count', None)
if reset is not None:
reset()
@ -319,22 +497,24 @@ class _BasicAuthHandler(urllib.request.HTTPBasicAuthHandler):
self.retried = 0
raise
class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
def http_error_401(self, req, fp, code, msg, headers):
_AddPasswordFromUserInput(self, msg, req)
return urllib.request.HTTPDigestAuthHandler.http_error_401(
self, req, fp, code, msg, headers)
self, req, fp, code, msg, headers)
def http_error_auth_reqed(self, auth_header, host, req, headers):
try:
old_add_header = req.add_header
def _add_header(name, val):
val = val.replace('\n', '')
old_add_header(name, val)
req.add_header = _add_header
return urllib.request.AbstractDigestAuthHandler.http_error_auth_reqed(
self, auth_header, host, req, headers)
except:
self, auth_header, host, req, headers)
except Exception:
reset = getattr(self, 'reset_retry_count', None)
if reset is not None:
reset()
@ -342,6 +522,7 @@ class _DigestAuthHandler(urllib.request.HTTPDigestAuthHandler):
self.retried = 0
raise
class _KerberosAuthHandler(urllib.request.BaseHandler):
def __init__(self):
self.retried = 0
@ -360,7 +541,7 @@ class _KerberosAuthHandler(urllib.request.BaseHandler):
if self.retried > 3:
raise urllib.request.HTTPError(req.get_full_url(), 401,
"Negotiate auth failed", headers, None)
"Negotiate auth failed", headers, None)
else:
self.retried += 1
@ -376,7 +557,7 @@ class _KerberosAuthHandler(urllib.request.BaseHandler):
return response
except kerberos.GSSError:
return None
except:
except Exception:
self.reset_retry_count()
raise
finally:
@ -422,6 +603,7 @@ class _KerberosAuthHandler(urllib.request.BaseHandler):
kerberos.authGSSClientClean(self.context)
self.context = None
def init_http():
handlers = [_UserAgentHandler()]
@ -430,7 +612,7 @@ def init_http():
n = netrc.netrc()
for host in n.hosts:
p = n.hosts[host]
mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2])
mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2])
mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2])
except netrc.NetrcParseError:
pass
@ -449,6 +631,7 @@ def init_http():
handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
urllib.request.install_opener(urllib.request.build_opener(*handlers))
def _Main(argv):
result = 0
@ -470,12 +653,16 @@ def _Main(argv):
repo = _Repo(opt.repodir)
try:
try:
init_ssh()
init_http()
result = repo._Run(argv) or 0
finally:
close_ssh()
init_http()
name, gopts, argv = repo._ParseArgs(argv)
run = lambda: repo._Run(name, gopts, argv) or 0
if gopts.trace_python:
import trace
tracer = trace.Trace(count=False, trace=True, timing=True,
ignoredirs=set(sys.path[1:]))
result = tracer.runfunc(run)
else:
result = run()
except KeyboardInterrupt:
print('aborted by user', file=sys.stderr)
result = 1
@ -488,13 +675,15 @@ def _Main(argv):
argv = list(sys.argv)
argv.extend(rce.extra_args)
try:
os.execv(__file__, argv)
os.execv(sys.executable, [__file__] + argv)
except OSError as e:
print('fatal: cannot restart repo after upgrade', file=sys.stderr)
print('fatal: %s' % e, file=sys.stderr)
result = 128
TerminatePager()
sys.exit(result)
if __name__ == '__main__':
_Main(sys.argv[1:])

49
man/repo-abandon.1 Normal file
View File

@ -0,0 +1,49 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo abandon" "Repo Manual"
.SH NAME
repo \- repo abandon - manual page for repo abandon
.SH SYNOPSIS
.B repo
\fI\,abandon \/\fR[\fI\,--all | <branchname>\/\fR] [\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Permanently abandon a development branch
.PP
This subcommand permanently abandons a development branch by
deleting it (and all its history) from your local repository.
.PP
It is equivalent to "git branch \fB\-D\fR <branchname>".
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-\-all\fR
delete all branches in all projects
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help abandon` to view the detailed manual.

1
man/repo-branch.1 Normal file
View File

@ -0,0 +1 @@
.so man1/repo-branches.1

72
man/repo-branches.1 Normal file
View File

@ -0,0 +1,72 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo branches" "Repo Manual"
.SH NAME
repo \- repo branches - manual page for repo branches
.SH SYNOPSIS
.B repo
\fI\,branches \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
View current topic branches
.PP
Summarizes the currently available topic branches.
.PP
# Branch Display
.PP
The branch display output by this command is organized into four
columns of information; for example:
.TP
*P nocolor
| in repo
.TP
repo2
|
.PP
The first column contains a * if the branch is the currently
checked out branch in any of the specified projects, or a blank
if no project has the branch checked out.
.PP
The second column contains either blank, p or P, depending upon
the upload status of the branch.
.IP
(blank): branch not yet published by repo upload
.IP
P: all commits were published by repo upload
p: only some commits were published by repo upload
.PP
The third column contains the branch name.
.PP
The fourth column (after the | separator) lists the projects that
the branch appears in, or does not appear in. If no project list
is shown, then the branch appears in all projects.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help branches` to view the detailed manual.

49
man/repo-checkout.1 Normal file
View File

@ -0,0 +1,49 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo checkout" "Repo Manual"
.SH NAME
repo \- repo checkout - manual page for repo checkout
.SH SYNOPSIS
.B repo
\fI\,checkout <branchname> \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Checkout a branch for development
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help checkout` to view the detailed manual.
.SH DETAILS
.PP
The 'repo checkout' command checks out an existing branch that was previously
created by 'repo start'.
.PP
The command is equivalent to:
.IP
repo forall [<project>...] \fB\-c\fR git checkout <branchname>

41
man/repo-cherry-pick.1 Normal file
View File

@ -0,0 +1,41 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo cherry-pick" "Repo Manual"
.SH NAME
repo \- repo cherry-pick - manual page for repo cherry-pick
.SH SYNOPSIS
.B repo
\fI\,cherry-pick <sha1>\/\fR
.SH DESCRIPTION
Summary
.PP
Cherry\-pick a change.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help cherry\-pick` to view the detailed manual.
.SH DETAILS
.PP
\&'repo cherry\-pick' cherry\-picks a change from one branch to another. The change
id will be updated, and a reference to the old change id will be added.

48
man/repo-diff.1 Normal file
View File

@ -0,0 +1,48 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo diff" "Repo Manual"
.SH NAME
repo \- repo diff - manual page for repo diff
.SH SYNOPSIS
.B repo
\fI\,diff \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Show changes between commit and working tree
.PP
The \fB\-u\fR option causes 'repo diff' to generate diff output with file paths
relative to the repository root, so the output can be applied
to the Unix 'patch' command.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-u\fR, \fB\-\-absolute\fR
paths are relative to the repository root
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help diff` to view the detailed manual.

74
man/repo-diffmanifests.1 Normal file
View File

@ -0,0 +1,74 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo diffmanifests" "Repo Manual"
.SH NAME
repo \- repo diffmanifests - manual page for repo diffmanifests
.SH SYNOPSIS
.B repo
\fI\,diffmanifests manifest1.xml \/\fR[\fI\,manifest2.xml\/\fR] [\fI\,options\/\fR]
.SH DESCRIPTION
Summary
.PP
Manifest diff utility
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-\-raw\fR
display raw diff
.TP
\fB\-\-no\-color\fR
does not display the diff in color
.TP
\fB\-\-pretty\-format=\fR<FORMAT>
print the log using a custom git pretty format string
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help diffmanifests` to view the detailed manual.
.SH DETAILS
.PP
The repo diffmanifests command shows differences between project revisions of
manifest1 and manifest2. if manifest2 is not specified, current manifest.xml
will be used instead. Both absolute and relative paths may be used for
manifests. Relative paths start from project's ".repo/manifests" folder.
.PP
The \fB\-\-raw\fR option Displays the diff in a way that facilitates parsing, the
project pattern will be <status> <path> <revision from> [<revision to>] and the
commit pattern will be <status> <onelined log> with status values respectively :
.IP
A = Added project
R = Removed project
C = Changed project
U = Project with unreachable revision(s) (revision(s) not found)
.PP
for project, and
.IP
A = Added commit
R = Removed commit
.PP
for a commit.
.PP
Only changed projects may contain commits, and commit status always starts with
a space, and are part of last printed project. Unreachable revisions may occur
if project is not up to date or if repo has not been initialized with all the
groups, in which case some projects won't be synced and their revisions won't be
found.

57
man/repo-download.1 Normal file
View File

@ -0,0 +1,57 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo download" "Repo Manual"
.SH NAME
repo \- repo download - manual page for repo download
.SH SYNOPSIS
.B repo
\fI\,download {\/\fR[\fI\,project\/\fR] \fI\,change\/\fR[\fI\,/patchset\/\fR]\fI\,}\/\fR...
.SH DESCRIPTION
Summary
.PP
Download and checkout a change
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-b\fR BRANCH, \fB\-\-branch\fR=\fI\,BRANCH\/\fR
create a new branch first
.TP
\fB\-c\fR, \fB\-\-cherry\-pick\fR
cherry\-pick instead of checkout
.TP
\fB\-x\fR, \fB\-\-record\-origin\fR
pass \fB\-x\fR when cherry\-picking
.TP
\fB\-r\fR, \fB\-\-revert\fR
revert instead of checkout
.TP
\fB\-f\fR, \fB\-\-ff\-only\fR
force fast\-forward merge
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help download` to view the detailed manual.
.SH DETAILS
.PP
The 'repo download' command downloads a change from the review system and makes
it available in your project's local working directory. If no project is
specified try to use current directory as a project.

146
man/repo-forall.1 Normal file
View File

@ -0,0 +1,146 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo forall" "Repo Manual"
.SH NAME
repo \- repo forall - manual page for repo forall
.SH SYNOPSIS
.B repo
\fI\,forall \/\fR[\fI\,<project>\/\fR...] \fI\,-c <command> \/\fR[\fI\,<arg>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Run a shell command in each project
.PP
repo forall \fB\-r\fR str1 [str2] ... \fB\-c\fR <command> [<arg>...]
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-r\fR, \fB\-\-regex\fR
execute the command only on projects matching regex or
wildcard expression
.TP
\fB\-i\fR, \fB\-\-inverse\-regex\fR
execute the command only on projects not matching
regex or wildcard expression
.TP
\fB\-g\fR GROUPS, \fB\-\-groups\fR=\fI\,GROUPS\/\fR
execute the command only on projects matching the
specified groups
.TP
\fB\-c\fR, \fB\-\-command\fR
command (and arguments) to execute
.TP
\fB\-e\fR, \fB\-\-abort\-on\-errors\fR
abort if a command exits unsuccessfully
.TP
\fB\-\-ignore\-missing\fR
silently skip & do not exit non\-zero due missing
checkouts
.TP
\fB\-\-interactive\fR
force interactive usage
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.TP
\fB\-p\fR
show project headers before output
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help forall` to view the detailed manual.
.SH DETAILS
.PP
Executes the same shell command in each project.
.PP
The \fB\-r\fR option allows running the command only on projects matching regex or
wildcard expression.
.PP
By default, projects are processed non\-interactively in parallel. If you want to
run interactive commands, make sure to pass \fB\-\-interactive\fR to force \fB\-\-jobs\fR 1.
While the processing order of projects is not guaranteed, the order of project
output is stable.
.PP
Output Formatting
.PP
The \fB\-p\fR option causes 'repo forall' to bind pipes to the command's stdin, stdout
and stderr streams, and pipe all output into a continuous stream that is
displayed in a single pager session. Project headings are inserted before the
output of each command is displayed. If the command produces no output in a
project, no heading is displayed.
.PP
The formatting convention used by \fB\-p\fR is very suitable for some types of
searching, e.g. `repo forall \fB\-p\fR \fB\-c\fR git log \fB\-SFoo\fR` will print all commits that
add or remove references to Foo.
.PP
The \fB\-v\fR option causes 'repo forall' to display stderr messages if a command
produces output only on stderr. Normally the \fB\-p\fR option causes command output to
be suppressed until the command produces at least one byte of output on stdout.
.PP
Environment
.PP
pwd is the project's working directory. If the current client is a mirror
client, then pwd is the Git repository.
.PP
REPO_PROJECT is set to the unique name of the project.
.PP
REPO_PATH is the path relative the the root of the client.
.PP
REPO_OUTERPATH is the path of the sub manifest's root relative to the root of
the client.
.PP
REPO_INNERPATH is the path relative to the root of the sub manifest.
.PP
REPO_REMOTE is the name of the remote system from the manifest.
.PP
REPO_LREV is the name of the revision from the manifest, translated to a local
tracking branch. If you need to pass the manifest revision to a locally executed
git command, use REPO_LREV.
.PP
REPO_RREV is the name of the revision from the manifest, exactly as written in
the manifest.
.PP
REPO_COUNT is the total number of projects being iterated.
.PP
REPO_I is the current (1\-based) iteration count. Can be used in conjunction with
REPO_COUNT to add a simple progress indicator to your command.
.PP
REPO__* are any extra environment variables, specified by the "annotation"
element under any project element. This can be useful for differentiating trees
based on user\-specific criteria, or simply annotating tree details.
.PP
shell positional arguments ($1, $2, .., $#) are set to any arguments following
<command>.
.PP
Example: to list projects:
.IP
repo forall \fB\-c\fR 'echo $REPO_PROJECT'
.PP
Notice that $REPO_PROJECT is quoted to ensure it is expanded in the context of
running <command> instead of in the calling shell.
.PP
Unless \fB\-p\fR is used, stdin, stdout, stderr are inherited from the terminal and are
not redirected.
.PP
If \fB\-e\fR is used, when a command exits unsuccessfully, 'repo forall' will abort
without iterating through the remaining projects.

44
man/repo-gitc-delete.1 Normal file
View File

@ -0,0 +1,44 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo gitc-delete" "Repo Manual"
.SH NAME
repo \- repo gitc-delete - manual page for repo gitc-delete
.SH SYNOPSIS
.B repo
\fI\,gitc-delete\/\fR
.SH DESCRIPTION
Summary
.PP
Delete a GITC Client.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-f\fR, \fB\-\-force\fR
force the deletion (no prompt)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help gitc\-delete` to view the detailed manual.
.SH DETAILS
.PP
This subcommand deletes the current GITC client, deleting the GITC manifest and
all locally downloaded sources.

175
man/repo-gitc-init.1 Normal file
View File

@ -0,0 +1,175 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "August 2022" "repo gitc-init" "Repo Manual"
.SH NAME
repo \- repo gitc-init - manual page for repo gitc-init
.SH SYNOPSIS
.B repo
\fI\,gitc-init \/\fR[\fI\,options\/\fR] [\fI\,client name\/\fR]
.SH DESCRIPTION
Summary
.PP
Initialize a GITC Client.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Manifest options:
.TP
\fB\-u\fR URL, \fB\-\-manifest\-url\fR=\fI\,URL\/\fR
manifest repository location
.TP
\fB\-b\fR REVISION, \fB\-\-manifest\-branch\fR=\fI\,REVISION\/\fR
manifest branch or revision (use HEAD for default)
.TP
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
initial manifest file
.TP
\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
restrict manifest projects to ones with specified
group(s) [default|all|G1,G2,G3|G4,\-G5,\-G6]
.TP
\fB\-p\fR PLATFORM, \fB\-\-platform\fR=\fI\,PLATFORM\/\fR
restrict manifest projects to ones with a specified
platform group [auto|all|none|linux|darwin|...]
.TP
\fB\-\-submodules\fR
sync any submodules associated with the manifest repo
.TP
\fB\-\-standalone\-manifest\fR
download the manifest as a static file rather then
create a git checkout of the manifest repo
.TP
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
create a shallow clone of the manifest repo with given
depth; see git clone (default: 1)
.SS Manifest (only) checkout options:
.TP
\fB\-\-current\-branch\fR
fetch only current manifest branch from server
(default)
.TP
\fB\-\-no\-current\-branch\fR
fetch all manifest branches from server
.TP
\fB\-\-tags\fR
fetch tags in the manifest
.TP
\fB\-\-no\-tags\fR
don't fetch tags in the manifest
.SS Checkout modes:
.TP
\fB\-\-mirror\fR
create a replica of the remote repositories rather
than a client working directory
.TP
\fB\-\-archive\fR
checkout an archive instead of a git repository for
each project. See git archive.
.TP
\fB\-\-worktree\fR
use git\-worktree to manage projects
.SS Project checkout optimizations:
.TP
\fB\-\-reference\fR=\fI\,DIR\/\fR
location of mirror directory
.TP
\fB\-\-dissociate\fR
dissociate from reference mirrors after clone
.TP
\fB\-\-depth\fR=\fI\,DEPTH\/\fR
create a shallow clone with given depth; see git clone
.TP
\fB\-\-partial\-clone\fR
perform partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
.TP
\fB\-\-no\-partial\-clone\fR
disable use of partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
.TP
\fB\-\-partial\-clone\-exclude\fR=\fI\,PARTIAL_CLONE_EXCLUDE\/\fR
exclude the specified projects (a comma\-delimited
project names) from partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
.TP
\fB\-\-clone\-filter\fR=\fI\,CLONE_FILTER\/\fR
filter for use with \fB\-\-partial\-clone\fR [default:
blob:none]
.TP
\fB\-\-use\-superproject\fR
use the manifest superproject to sync projects;
implies \fB\-c\fR
.TP
\fB\-\-no\-use\-superproject\fR
disable use of manifest superprojects
.TP
\fB\-\-clone\-bundle\fR
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
not \fB\-\-partial\-clone\fR)
.TP
\fB\-\-no\-clone\-bundle\fR
disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
\fB\-\-partial\-clone\fR)
.TP
\fB\-\-git\-lfs\fR
enable Git LFS support
.TP
\fB\-\-no\-git\-lfs\fR
disable Git LFS support
.SS repo Version options:
.TP
\fB\-\-repo\-url\fR=\fI\,URL\/\fR
repo repository location ($REPO_URL)
.TP
\fB\-\-repo\-rev\fR=\fI\,REV\/\fR
repo branch or revision ($REPO_REV)
.TP
\fB\-\-no\-repo\-verify\fR
do not verify repo source code
.SS Other options:
.TP
\fB\-\-config\-name\fR
Always prompt for name/e\-mail
.SS GITC options:
.TP
\fB\-f\fR MANIFEST_FILE, \fB\-\-manifest\-file\fR=\fI\,MANIFEST_FILE\/\fR
Optional manifest file to use for this GITC client.
.TP
\fB\-c\fR GITC_CLIENT, \fB\-\-gitc\-client\fR=\fI\,GITC_CLIENT\/\fR
Name of the gitc_client instance to create or modify.
.SS Multi\-manifest:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help gitc\-init` to view the detailed manual.
.SH DETAILS
.PP
The 'repo gitc\-init' command is ran to initialize a new GITC client for use with
the GITC file system.
.PP
This command will setup the client directory, initialize repo, just like repo
init does, and then downloads the manifest collection and installs it in the
\&.repo/directory of the GITC client.
.PP
Once this is done, a GITC manifest is generated by pulling the HEAD SHA for each
project and generates the properly formatted XML file and installs it as
\&.manifest in the GITC client directory.
.PP
The \fB\-c\fR argument is required to specify the GITC client name.
.PP
The optional \fB\-f\fR argument can be used to specify the manifest file to use for
this GITC client.

132
man/repo-grep.1 Normal file
View File

@ -0,0 +1,132 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo grep" "Repo Manual"
.SH NAME
repo \- repo grep - manual page for repo grep
.SH SYNOPSIS
.B repo
\fI\,grep {pattern | -e pattern} \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Print lines matching a pattern
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options:
.TP
\fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.SS Sources:
.TP
\fB\-\-cached\fR
Search the index, instead of the work tree
.TP
\fB\-r\fR TREEish, \fB\-\-revision\fR=\fI\,TREEish\/\fR
Search TREEish, instead of the work tree
.SS Pattern:
.TP
\fB\-e\fR PATTERN
Pattern to search for
.TP
\fB\-i\fR, \fB\-\-ignore\-case\fR
Ignore case differences
.TP
\fB\-a\fR, \fB\-\-text\fR
Process binary files as if they were text
.TP
\fB\-I\fR
Don't match the pattern in binary files
.TP
\fB\-w\fR, \fB\-\-word\-regexp\fR
Match the pattern only at word boundaries
.TP
\fB\-v\fR, \fB\-\-invert\-match\fR
Select non\-matching lines
.TP
\fB\-G\fR, \fB\-\-basic\-regexp\fR
Use POSIX basic regexp for patterns (default)
.TP
\fB\-E\fR, \fB\-\-extended\-regexp\fR
Use POSIX extended regexp for patterns
.TP
\fB\-F\fR, \fB\-\-fixed\-strings\fR
Use fixed strings (not regexp) for pattern
.SS Pattern Grouping:
.TP
\fB\-\-all\-match\fR
Limit match to lines that have all patterns
.TP
\fB\-\-and\fR, \fB\-\-or\fR, \fB\-\-not\fR
Boolean operators to combine patterns
.TP
\-(, \-)
Boolean operator grouping
.SS Output:
.TP
\fB\-n\fR
Prefix the line number to matching lines
.TP
\fB\-C\fR CONTEXT
Show CONTEXT lines around match
.TP
\fB\-B\fR CONTEXT
Show CONTEXT lines before match
.TP
\fB\-A\fR CONTEXT
Show CONTEXT lines after match
.TP
\fB\-l\fR, \fB\-\-name\-only\fR, \fB\-\-files\-with\-matches\fR
Show only file names containing matching lines
.TP
\fB\-L\fR, \fB\-\-files\-without\-match\fR
Show only file names not containing matching lines
.PP
Run `repo help grep` to view the detailed manual.
.SH DETAILS
.PP
Search for the specified patterns in all project files.
.PP
Boolean Options
.PP
The following options can appear as often as necessary to express the pattern to
locate:
.HP
\fB\-e\fR PATTERN
.HP
\fB\-\-and\fR, \fB\-\-or\fR, \fB\-\-not\fR, \-(, \-)
.PP
Further, the \fB\-r\fR/\-\-revision option may be specified multiple times in order to
scan multiple trees. If the same file matches in more than one tree, only the
first result is reported, prefixed by the revision name it was found under.
.PP
Examples
.PP
Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
.IP
repo grep \fB\-e\fR '#define' \fB\-\-and\fR \-\e( \fB\-e\fR MAX_PATH \fB\-e\fR PATH_MAX \e)
.PP
Look for a line that has 'NODE' or 'Unexpected' in files that contain a line
that matches both expressions:
.IP
repo grep \fB\-\-all\-match\fR \fB\-e\fR NODE \fB\-e\fR Unexpected

46
man/repo-help.1 Normal file
View File

@ -0,0 +1,46 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo help" "Repo Manual"
.SH NAME
repo \- repo help - manual page for repo help
.SH SYNOPSIS
.B repo
\fI\,help \/\fR[\fI\,--all|command\/\fR]
.SH DESCRIPTION
Summary
.PP
Display detailed help on a command
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-a\fR, \fB\-\-all\fR
show the complete list of commands
.TP
\fB\-\-help\-all\fR
show the \fB\-\-help\fR of all commands
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help help` to view the detailed manual.
.SH DETAILS
.PP
Displays detailed usage information about a command.

53
man/repo-info.1 Normal file
View File

@ -0,0 +1,53 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo info" "Repo Manual"
.SH NAME
repo \- repo info - manual page for repo info
.SH SYNOPSIS
.B repo
\fI\,info \/\fR[\fI\,-dl\/\fR] [\fI\,-o \/\fR[\fI\,-c\/\fR]] [\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Get info on the manifest branch, current branch or unmerged branches
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-d\fR, \fB\-\-diff\fR
show full info and commit diff including remote
branches
.TP
\fB\-o\fR, \fB\-\-overview\fR
show overview of all local commits
.TP
\fB\-c\fR, \fB\-\-current\-branch\fR
consider only checked out branches
.TP
\fB\-\-no\-current\-branch\fR
consider all local branches
.TP
\fB\-l\fR, \fB\-\-local\-only\fR
disable all remote operations
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help info` to view the detailed manual.

195
man/repo-init.1 Normal file
View File

@ -0,0 +1,195 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "August 2022" "repo init" "Repo Manual"
.SH NAME
repo \- repo init - manual page for repo init
.SH SYNOPSIS
.B repo
\fI\,init \/\fR[\fI\,options\/\fR] [\fI\,manifest url\/\fR]
.SH DESCRIPTION
Summary
.PP
Initialize a repo client checkout in the current directory
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Manifest options:
.TP
\fB\-u\fR URL, \fB\-\-manifest\-url\fR=\fI\,URL\/\fR
manifest repository location
.TP
\fB\-b\fR REVISION, \fB\-\-manifest\-branch\fR=\fI\,REVISION\/\fR
manifest branch or revision (use HEAD for default)
.TP
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
initial manifest file
.TP
\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
restrict manifest projects to ones with specified
group(s) [default|all|G1,G2,G3|G4,\-G5,\-G6]
.TP
\fB\-p\fR PLATFORM, \fB\-\-platform\fR=\fI\,PLATFORM\/\fR
restrict manifest projects to ones with a specified
platform group [auto|all|none|linux|darwin|...]
.TP
\fB\-\-submodules\fR
sync any submodules associated with the manifest repo
.TP
\fB\-\-standalone\-manifest\fR
download the manifest as a static file rather then
create a git checkout of the manifest repo
.TP
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
create a shallow clone of the manifest repo with given
depth; see git clone (default: 1)
.SS Manifest (only) checkout options:
.TP
\fB\-c\fR, \fB\-\-current\-branch\fR
fetch only current manifest branch from server
(default)
.TP
\fB\-\-no\-current\-branch\fR
fetch all manifest branches from server
.TP
\fB\-\-tags\fR
fetch tags in the manifest
.TP
\fB\-\-no\-tags\fR
don't fetch tags in the manifest
.SS Checkout modes:
.TP
\fB\-\-mirror\fR
create a replica of the remote repositories rather
than a client working directory
.TP
\fB\-\-archive\fR
checkout an archive instead of a git repository for
each project. See git archive.
.TP
\fB\-\-worktree\fR
use git\-worktree to manage projects
.SS Project checkout optimizations:
.TP
\fB\-\-reference\fR=\fI\,DIR\/\fR
location of mirror directory
.TP
\fB\-\-dissociate\fR
dissociate from reference mirrors after clone
.TP
\fB\-\-depth\fR=\fI\,DEPTH\/\fR
create a shallow clone with given depth; see git clone
.TP
\fB\-\-partial\-clone\fR
perform partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
.TP
\fB\-\-no\-partial\-clone\fR
disable use of partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
.TP
\fB\-\-partial\-clone\-exclude\fR=\fI\,PARTIAL_CLONE_EXCLUDE\/\fR
exclude the specified projects (a comma\-delimited
project names) from partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
.TP
\fB\-\-clone\-filter\fR=\fI\,CLONE_FILTER\/\fR
filter for use with \fB\-\-partial\-clone\fR [default:
blob:none]
.TP
\fB\-\-use\-superproject\fR
use the manifest superproject to sync projects;
implies \fB\-c\fR
.TP
\fB\-\-no\-use\-superproject\fR
disable use of manifest superprojects
.TP
\fB\-\-clone\-bundle\fR
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
not \fB\-\-partial\-clone\fR)
.TP
\fB\-\-no\-clone\-bundle\fR
disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
\fB\-\-partial\-clone\fR)
.TP
\fB\-\-git\-lfs\fR
enable Git LFS support
.TP
\fB\-\-no\-git\-lfs\fR
disable Git LFS support
.SS repo Version options:
.TP
\fB\-\-repo\-url\fR=\fI\,URL\/\fR
repo repository location ($REPO_URL)
.TP
\fB\-\-repo\-rev\fR=\fI\,REV\/\fR
repo branch or revision ($REPO_REV)
.TP
\fB\-\-no\-repo\-verify\fR
do not verify repo source code
.SS Other options:
.TP
\fB\-\-config\-name\fR
Always prompt for name/e\-mail
.SS Multi\-manifest:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help init` to view the detailed manual.
.SH DETAILS
.PP
The 'repo init' command is run once to install and initialize repo. The latest
repo source code and manifest collection is downloaded from the server and is
installed in the .repo/ directory in the current working directory.
.PP
When creating a new checkout, the manifest URL is the only required setting. It
may be specified using the \fB\-\-manifest\-url\fR option, or as the first optional
argument.
.PP
The optional \fB\-b\fR argument can be used to select the manifest branch to checkout
and use. If no branch is specified, the remote's default branch is used. This is
equivalent to using \fB\-b\fR HEAD.
.PP
The optional \fB\-m\fR argument can be used to specify an alternate manifest to be
used. If no manifest is specified, the manifest default.xml will be used.
.PP
If the \fB\-\-standalone\-manifest\fR argument is set, the manifest will be downloaded
directly from the specified \fB\-\-manifest\-url\fR as a static file (rather than setting
up a manifest git checkout). With \fB\-\-standalone\-manifest\fR, the manifest will be
fully static and will not be re\-downloaded during subsesquent `repo init` and
`repo sync` calls.
.PP
The \fB\-\-reference\fR option can be used to point to a directory that has the content
of a \fB\-\-mirror\fR sync. This will make the working directory use as much data as
possible from the local reference directory when fetching from the server. This
will make the sync go a lot faster by reducing data traffic on the network.
.PP
The \fB\-\-dissociate\fR option can be used to borrow the objects from the directory
specified with the \fB\-\-reference\fR option only to reduce network transfer, and stop
borrowing from them after a first clone is made by making necessary local copies
of borrowed objects.
.PP
The \fB\-\-no\-clone\-bundle\fR option disables any attempt to use \fI\,$URL/clone.bundle\/\fP to
bootstrap a new Git repository from a resumeable bundle file on a content
delivery network. This may be necessary if there are problems with the local
Python HTTP client or proxy configuration, but the Git binary works.
.PP
Switching Manifest Branches
.PP
To switch to another manifest branch, `repo init \fB\-b\fR otherbranch` may be used in
an existing client. However, as this only updates the manifest, a subsequent
`repo sync` (or `repo sync \fB\-d\fR`) is necessary to update the working directory
files.

74
man/repo-list.1 Normal file
View File

@ -0,0 +1,74 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo list" "Repo Manual"
.SH NAME
repo \- repo list - manual page for repo list
.SH SYNOPSIS
.B repo
\fI\,list \/\fR[\fI\,-f\/\fR] [\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
List projects and their associated directories
.PP
repo list [\-f] \fB\-r\fR str1 [str2]...
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-r\fR, \fB\-\-regex\fR
filter the project list based on regex or wildcard
matching of strings
.TP
\fB\-g\fR GROUPS, \fB\-\-groups\fR=\fI\,GROUPS\/\fR
filter the project list based on the groups the
project is in
.TP
\fB\-a\fR, \fB\-\-all\fR
show projects regardless of checkout state
.TP
\fB\-n\fR, \fB\-\-name\-only\fR
display only the name of the repository
.TP
\fB\-p\fR, \fB\-\-path\-only\fR
display only the path of the repository
.TP
\fB\-f\fR, \fB\-\-fullpath\fR
display the full work tree path instead of the
relative path
.TP
\fB\-\-relative\-to\fR=\fI\,PATH\/\fR
display paths relative to this one (default: top of
repo client checkout)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help list` to view the detailed manual.
.SH DETAILS
.PP
List all projects; pass '.' to list the project for the cwd.
.PP
By default, only projects that currently exist in the checkout are shown. If you
want to list all projects (using the specified filter settings), use the \fB\-\-all\fR
option. If you want to show all projects regardless of the manifest groups, then
also pass \fB\-\-groups\fR all.
.PP
This is similar to running: repo forall \fB\-c\fR 'echo "$REPO_PATH : $REPO_PROJECT"'.

643
man/repo-manifest.1 Normal file
View File

@ -0,0 +1,643 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo manifest" "Repo Manual"
.SH NAME
repo \- repo manifest - manual page for repo manifest
.SH SYNOPSIS
.B repo
\fI\,manifest \/\fR[\fI\,-o {-|NAME.xml}\/\fR] [\fI\,-m MANIFEST.xml\/\fR] [\fI\,-r\/\fR]
.SH DESCRIPTION
Summary
.PP
Manifest inspection utility
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-r\fR, \fB\-\-revision\-as\-HEAD\fR
save revisions as current HEAD
.TP
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
temporary manifest to use for this sync
.TP
\fB\-\-suppress\-upstream\-revision\fR
if in \fB\-r\fR mode, do not write the upstream field (only
of use if the branch names for a sha1 manifest are
sensitive)
.TP
\fB\-\-suppress\-dest\-branch\fR
if in \fB\-r\fR mode, do not write the dest\-branch field
(only of use if the branch names for a sha1 manifest
are sensitive)
.TP
\fB\-\-json\fR
output manifest in JSON format (experimental)
.TP
\fB\-\-pretty\fR
format output for humans to read
.TP
\fB\-\-no\-local\-manifests\fR
ignore local manifests
.TP
\fB\-o\fR \-|NAME.xml, \fB\-\-output\-file\fR=\fI\,\-\/\fR|NAME.xml
file to save the manifest to. (Filename prefix for
multi\-tree.)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help manifest` to view the detailed manual.
.SH DETAILS
.PP
With the \fB\-o\fR option, exports the current manifest for inspection. The manifest
and (if present) local_manifests/ are combined together to produce a single
manifest file. This file can be stored in a Git repository for use during future
\&'repo init' invocations.
.PP
The \fB\-r\fR option can be used to generate a manifest file with project revisions set
to the current commit hash. These are known as "revision locked manifests", as
they don't follow a particular branch. In this case, the 'upstream' attribute is
set to the ref we were on when the manifest was generated. The 'dest\-branch'
attribute is set to indicate the remote ref to push changes to via 'repo
upload'.
.PP
repo Manifest Format
.PP
A repo manifest describes the structure of a repo client; that is the
directories that are visible and where they should be obtained from with git.
.PP
The basic structure of a manifest is a bare Git repository holding a single
`default.xml` XML file in the top level directory.
.PP
Manifests are inherently version controlled, since they are kept within a Git
repository. Updates to manifests are automatically obtained by clients during
`repo sync`.
.PP
[TOC]
.PP
XML File Format
.PP
A manifest XML file (e.g. `default.xml`) roughly conforms to the following DTD:
.PP
```xml <!DOCTYPE manifest [
.TP
<!ELEMENT manifest (notice?,
remote*,
default?,
manifest\-server?,
submanifest*?,
remove\-project*,
project*,
extend\-project*,
repo\-hooks?,
superproject?,
contactinfo?,
include*)>
.IP
<!ELEMENT notice (#PCDATA)>
.IP
<!ELEMENT remote (annotation*)>
<!ATTLIST remote name ID #REQUIRED>
<!ATTLIST remote alias CDATA #IMPLIED>
<!ATTLIST remote fetch CDATA #REQUIRED>
<!ATTLIST remote pushurl CDATA #IMPLIED>
<!ATTLIST remote review CDATA #IMPLIED>
<!ATTLIST remote revision CDATA #IMPLIED>
.IP
<!ELEMENT default EMPTY>
<!ATTLIST default remote IDREF #IMPLIED>
<!ATTLIST default revision CDATA #IMPLIED>
<!ATTLIST default dest\-branch CDATA #IMPLIED>
<!ATTLIST default upstream CDATA #IMPLIED>
<!ATTLIST default sync\-j CDATA #IMPLIED>
<!ATTLIST default sync\-c CDATA #IMPLIED>
<!ATTLIST default sync\-s CDATA #IMPLIED>
<!ATTLIST default sync\-tags CDATA #IMPLIED>
.IP
<!ELEMENT manifest\-server EMPTY>
<!ATTLIST manifest\-server url CDATA #REQUIRED>
.IP
<!ELEMENT submanifest EMPTY>
<!ATTLIST submanifest name ID #REQUIRED>
<!ATTLIST submanifest remote IDREF #IMPLIED>
<!ATTLIST submanifest project CDATA #IMPLIED>
<!ATTLIST submanifest manifest\-name CDATA #IMPLIED>
<!ATTLIST submanifest revision CDATA #IMPLIED>
<!ATTLIST submanifest path CDATA #IMPLIED>
<!ATTLIST submanifest groups CDATA #IMPLIED>
<!ATTLIST submanifest default\-groups CDATA #IMPLIED>
.TP
<!ELEMENT project (annotation*,
project*,
copyfile*,
linkfile*)>
.TP
<!ATTLIST project name
CDATA #REQUIRED>
.TP
<!ATTLIST project path
CDATA #IMPLIED>
.TP
<!ATTLIST project remote
IDREF #IMPLIED>
.TP
<!ATTLIST project revision
CDATA #IMPLIED>
.IP
<!ATTLIST project dest\-branch CDATA #IMPLIED>
<!ATTLIST project groups CDATA #IMPLIED>
<!ATTLIST project sync\-c CDATA #IMPLIED>
<!ATTLIST project sync\-s CDATA #IMPLIED>
<!ATTLIST project sync\-tags CDATA #IMPLIED>
<!ATTLIST project upstream CDATA #IMPLIED>
<!ATTLIST project clone\-depth CDATA #IMPLIED>
<!ATTLIST project force\-path CDATA #IMPLIED>
.IP
<!ELEMENT annotation EMPTY>
<!ATTLIST annotation name CDATA #REQUIRED>
<!ATTLIST annotation value CDATA #REQUIRED>
<!ATTLIST annotation keep CDATA "true">
.IP
<!ELEMENT copyfile EMPTY>
<!ATTLIST copyfile src CDATA #REQUIRED>
<!ATTLIST copyfile dest CDATA #REQUIRED>
.IP
<!ELEMENT linkfile EMPTY>
<!ATTLIST linkfile src CDATA #REQUIRED>
<!ATTLIST linkfile dest CDATA #REQUIRED>
.IP
<!ELEMENT extend\-project EMPTY>
<!ATTLIST extend\-project name CDATA #REQUIRED>
<!ATTLIST extend\-project path CDATA #IMPLIED>
<!ATTLIST extend\-project dest\-path CDATA #IMPLIED>
<!ATTLIST extend\-project groups CDATA #IMPLIED>
<!ATTLIST extend\-project revision CDATA #IMPLIED>
<!ATTLIST extend\-project remote CDATA #IMPLIED>
.IP
<!ELEMENT remove\-project EMPTY>
<!ATTLIST remove\-project name CDATA #REQUIRED>
<!ATTLIST remove\-project optional CDATA #IMPLIED>
.IP
<!ELEMENT repo\-hooks EMPTY>
<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
<!ATTLIST repo\-hooks enabled\-list CDATA #REQUIRED>
.IP
<!ELEMENT superproject EMPTY>
<!ATTLIST superproject name CDATA #REQUIRED>
<!ATTLIST superproject remote IDREF #IMPLIED>
<!ATTLIST superproject revision CDATA #IMPLIED>
.IP
<!ELEMENT contactinfo EMPTY>
<!ATTLIST contactinfo bugurl CDATA #REQUIRED>
.IP
<!ELEMENT include EMPTY>
<!ATTLIST include name CDATA #REQUIRED>
<!ATTLIST include groups CDATA #IMPLIED>
.PP
]>
```
.PP
For compatibility purposes across repo releases, all unknown elements are
silently ignored. However, repo reserves all possible names for itself for
future use. If you want to use custom elements, the `x\-*` namespace is reserved
for that purpose, and repo guarantees to never allocate any corresponding names.
.PP
A description of the elements and their attributes follows.
.PP
Element manifest
.PP
The root element of the file.
.PP
Element notice
.PP
Arbitrary text that is displayed to users whenever `repo sync` finishes. The
content is simply passed through as it exists in the manifest.
.PP
Element remote
.PP
One or more remote elements may be specified. Each remote element specifies a
Git URL shared by one or more projects and (optionally) the Gerrit review server
those projects upload changes through.
.PP
Attribute `name`: A short name unique to this manifest file. The name specified
here is used as the remote name in each project's .git/config, and is therefore
automatically available to commands like `git fetch`, `git remote`, `git pull`
and `git push`.
.PP
Attribute `alias`: The alias, if specified, is used to override `name` to be set
as the remote name in each project's .git/config. Its value can be duplicated
while attribute `name` has to be unique in the manifest file. This helps each
project to be able to have same remote name which actually points to different
remote url.
.PP
Attribute `fetch`: The Git URL prefix for all projects which use this remote.
Each project's name is appended to this prefix to form the actual URL used to
clone the project.
.PP
Attribute `pushurl`: The Git "push" URL prefix for all projects which use this
remote. Each project's name is appended to this prefix to form the actual URL
used to "git push" the project. This attribute is optional; if not specified
then "git push" will use the same URL as the `fetch` attribute.
.PP
Attribute `review`: Hostname of the Gerrit server where reviews are uploaded to
by `repo upload`. This attribute is optional; if not specified then `repo
upload` will not function.
.PP
Attribute `revision`: Name of a Git branch (e.g. `main` or `refs/heads/main`).
Remotes with their own revision will override the default revision.
.PP
Element default
.PP
At most one default element may be specified. Its remote and revision attributes
are used when a project element does not specify its own remote or revision
attribute.
.PP
Attribute `remote`: Name of a previously defined remote element. Project
elements lacking a remote attribute of their own will use this remote.
.PP
Attribute `revision`: Name of a Git branch (e.g. `main` or `refs/heads/main`).
Project elements lacking their own revision attribute will use this revision.
.PP
Attribute `dest\-branch`: Name of a Git branch (e.g. `main`). Project elements
not setting their own `dest\-branch` will inherit this value. If this value is
not set, projects will use `revision` by default instead.
.PP
Attribute `upstream`: Name of the Git ref in which a sha1 can be found. Used
when syncing a revision locked manifest in \fB\-c\fR mode to avoid having to sync the
entire ref space. Project elements not setting their own `upstream` will inherit
this value.
.PP
Attribute `sync\-j`: Number of parallel jobs to use when synching.
.PP
Attribute `sync\-c`: Set to true to only sync the given Git branch (specified in
the `revision` attribute) rather than the whole ref space. Project elements
lacking a sync\-c element of their own will use this value.
.PP
Attribute `sync\-s`: Set to true to also sync sub\-projects.
.PP
Attribute `sync\-tags`: Set to false to only sync the given Git branch (specified
in the `revision` attribute) rather than the other ref tags.
.PP
Element manifest\-server
.PP
At most one manifest\-server may be specified. The url attribute is used to
specify the URL of a manifest server, which is an XML RPC service.
.PP
The manifest server should implement the following RPC methods:
.IP
GetApprovedManifest(branch, target)
.PP
Return a manifest in which each project is pegged to a known good revision for
the current branch and target. This is used by repo sync when the \fB\-\-smart\-sync\fR
option is given.
.PP
The target to use is defined by environment variables TARGET_PRODUCT and
TARGET_BUILD_VARIANT. These variables are used to create a string of the form
$TARGET_PRODUCT\-$TARGET_BUILD_VARIANT, e.g. passion\-userdebug. If one of those
variables or both are not present, the program will call GetApprovedManifest
without the target parameter and the manifest server should choose a reasonable
default target.
.IP
GetManifest(tag)
.PP
Return a manifest in which each project is pegged to the revision at the
specified tag. This is used by repo sync when the \fB\-\-smart\-tag\fR option is given.
.PP
Element submanifest
.PP
One or more submanifest elements may be specified. Each element describes a
single manifest to be checked out as a child.
.PP
Attribute `name`: A unique name (within the current (sub)manifest) for this
submanifest. It acts as a default for `revision` below. The same name can be
used for submanifests with different parent (sub)manifests.
.PP
Attribute `remote`: Name of a previously defined remote element. If not supplied
the remote given by the default element is used.
.PP
Attribute `project`: The manifest project name. The project's name is appended
onto its remote's fetch URL to generate the actual URL to configure the Git
remote with. The URL gets formed as:
.IP
${remote_fetch}/${project_name}.git
.PP
where ${remote_fetch} is the remote's fetch attribute and ${project_name} is the
project's name attribute. The suffix ".git" is always appended as repo assumes
the upstream is a forest of bare Git repositories. If the project has a parent
element, its name will be prefixed by the parent's.
.PP
The project name must match the name Gerrit knows, if Gerrit is being used for
code reviews.
.PP
`project` must not be empty, and may not be an absolute path or use "." or ".."
path components. It is always interpreted relative to the remote's fetch
settings, so if a different base path is needed, declare a different remote with
the new settings needed.
.PP
If not supplied the remote and project for this manifest will be used: `remote`
cannot be supplied.
.PP
Projects from a submanifest and its submanifests are added to the
submanifest::path:<path_prefix> group.
.PP
Attribute `manifest\-name`: The manifest filename in the manifest project. If not
supplied, `default.xml` is used.
.PP
Attribute `revision`: Name of a Git branch (e.g. "main" or "refs/heads/main"),
tag (e.g. "refs/tags/stable"), or a commit hash. If not supplied, `name` is
used.
.PP
Attribute `path`: An optional path relative to the top directory of the repo
client where the submanifest repo client top directory should be placed. If not
supplied, `revision` is used.
.PP
`path` may not be an absolute path or use "." or ".." path components.
.PP
Attribute `groups`: List of additional groups to which all projects in the
included submanifest belong. This appends and recurses, meaning all projects in
submanifests carry all parent submanifest groups. Same syntax as the
corresponding element of `project`.
.PP
Attribute `default\-groups`: The list of manifest groups to sync if no
`\-\-groups=` parameter was specified at init. When that list is empty, use this
list instead of "default" as the list of groups to sync.
.PP
Element project
.PP
One or more project elements may be specified. Each element describes a single
Git repository to be cloned into the repo client workspace. You may specify
Git\-submodules by creating a nested project. Git\-submodules will be
automatically recognized and inherit their parent's attributes, but those may be
overridden by an explicitly specified project element.
.PP
Attribute `name`: A unique name for this project. The project's name is appended
onto its remote's fetch URL to generate the actual URL to configure the Git
remote with. The URL gets formed as:
.IP
${remote_fetch}/${project_name}.git
.PP
where ${remote_fetch} is the remote's fetch attribute and ${project_name} is the
project's name attribute. The suffix ".git" is always appended as repo assumes
the upstream is a forest of bare Git repositories. If the project has a parent
element, its name will be prefixed by the parent's.
.PP
The project name must match the name Gerrit knows, if Gerrit is being used for
code reviews.
.PP
"name" must not be empty, and may not be an absolute path or use "." or ".."
path components. It is always interpreted relative to the remote's fetch
settings, so if a different base path is needed, declare a different remote with
the new settings needed. These restrictions are not enforced for [Local
Manifests].
.PP
Attribute `path`: An optional path relative to the top directory of the repo
client where the Git working directory for this project should be placed. If not
supplied the project "name" is used. If the project has a parent element, its
path will be prefixed by the parent's.
.PP
"path" may not be an absolute path or use "." or ".." path components. These
restrictions are not enforced for [Local Manifests].
.PP
If you want to place files into the root of the checkout (e.g. a README or
Makefile or another build script), use the [copyfile] or [linkfile] elements
instead.
.PP
Attribute `remote`: Name of a previously defined remote element. If not supplied
the remote given by the default element is used.
.PP
Attribute `revision`: Name of the Git branch the manifest wants to track for
this project. Names can be relative to refs/heads (e.g. just "main") or absolute
(e.g. "refs/heads/main"). Tags and/or explicit SHA\-1s should work in theory, but
have not been extensively tested. If not supplied the revision given by the
remote element is used if applicable, else the default element is used.
.PP
Attribute `dest\-branch`: Name of a Git branch (e.g. `main`). When using `repo
upload`, changes will be submitted for code review on this branch. If
unspecified both here and in the default element, `revision` is used instead.
.PP
Attribute `groups`: List of groups to which this project belongs, whitespace or
comma separated. All projects belong to the group "all", and each project
automatically belongs to a group of its name:`name` and path:`path`. E.g. for
`<project name="monkeys" path="barrel\-of"/>`, that project definition is
implicitly in the following manifest groups: default, name:monkeys, and
path:barrel\-of. If you place a project in the group "notdefault", it will not be
automatically downloaded by repo. If the project has a parent element, the
`name` and `path` here are the prefixed ones.
.PP
Attribute `sync\-c`: Set to true to only sync the given Git branch (specified in
the `revision` attribute) rather than the whole ref space.
.PP
Attribute `sync\-s`: Set to true to also sync sub\-projects.
.PP
Attribute `upstream`: Name of the Git ref in which a sha1 can be found. Used
when syncing a revision locked manifest in \fB\-c\fR mode to avoid having to sync the
entire ref space.
.PP
Attribute `clone\-depth`: Set the depth to use when fetching this project. If
specified, this value will override any value given to repo init with the
\fB\-\-depth\fR option on the command line.
.PP
Attribute `force\-path`: Set to true to force this project to create the local
mirror repository according to its `path` attribute (if supplied) rather than
the `name` attribute. This attribute only applies to the local mirrors syncing,
it will be ignored when syncing the projects in a client working directory.
.PP
Element extend\-project
.PP
Modify the attributes of the named project.
.PP
This element is mostly useful in a local manifest file, to modify the attributes
of an existing project without completely replacing the existing project
definition. This makes the local manifest more robust against changes to the
original manifest.
.PP
Attribute `path`: If specified, limit the change to projects checked out at the
specified path, rather than all projects with the given name.
.PP
Attribute `dest\-path`: If specified, a path relative to the top directory of the
repo client where the Git working directory for this project should be placed.
This is used to move a project in the checkout by overriding the existing `path`
setting.
.PP
Attribute `groups`: List of additional groups to which this project belongs.
Same syntax as the corresponding element of `project`.
.PP
Attribute `revision`: If specified, overrides the revision of the original
project. Same syntax as the corresponding element of `project`.
.PP
Attribute `remote`: If specified, overrides the remote of the original project.
Same syntax as the corresponding element of `project`.
.PP
Element annotation
.PP
Zero or more annotation elements may be specified as children of a project or
remote element. Each element describes a name\-value pair. For projects, this
name\-value pair will be exported into each project's environment during a
\&'forall' command, prefixed with `REPO__`. In addition, there is an optional
attribute "keep" which accepts the case insensitive values "true" (default) or
"false". This attribute determines whether or not the annotation will be kept
when exported with the manifest subcommand.
.PP
Element copyfile
.PP
Zero or more copyfile elements may be specified as children of a project
element. Each element describes a src\-dest pair of files; the "src" file will be
copied to the "dest" place during `repo sync` command.
.PP
"src" is project relative, "dest" is relative to the top of the tree. Copying
from paths outside of the project or to paths outside of the repo client is not
allowed.
.PP
"src" and "dest" must be files. Directories or symlinks are not allowed.
Intermediate paths must not be symlinks either.
.PP
Parent directories of "dest" will be automatically created if missing.
.PP
Element linkfile
.PP
It's just like copyfile and runs at the same time as copyfile but instead of
copying it creates a symlink.
.PP
The symlink is created at "dest" (relative to the top of the tree) and points to
the path specified by "src" which is a path in the project.
.PP
Parent directories of "dest" will be automatically created if missing.
.PP
The symlink target may be a file or directory, but it may not point outside of
the repo client.
.PP
Element remove\-project
.PP
Deletes the named project from the internal manifest table, possibly allowing a
subsequent project element in the same manifest file to replace the project with
a different source.
.PP
This element is mostly useful in a local manifest file, where the user can
remove a project, and possibly replace it with their own definition.
.PP
Attribute `optional`: Set to true to ignore remove\-project elements with no
matching `project` element.
.PP
Element repo\-hooks
.PP
NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks.
.PP
Only one repo\-hooks element may be specified at a time. Attempting to redefine
it will fail to parse.
.PP
Attribute `in\-project`: The project where the hooks are defined. The value must
match the `name` attribute (**not** the `path` attribute) of a previously
defined `project` element.
.PP
Attribute `enabled\-list`: List of hooks to use, whitespace or comma separated.
.PP
Element superproject
.PP
*** *Note*: This is currently a WIP. ***
.PP
NB: See the [git superprojects documentation](
https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects) for background
information.
.PP
This element is used to specify the URL of the superproject. It has "name" and
"remote" as atrributes. Only "name" is required while the others have reasonable
defaults. At most one superproject may be specified. Attempting to redefine it
will fail to parse.
.PP
Attribute `name`: A unique name for the superproject. This attribute has the
same meaning as project's name attribute. See the [element
project](#element\-project) for more information.
.PP
Attribute `remote`: Name of a previously defined remote element. If not supplied
the remote given by the default element is used.
.PP
Attribute `revision`: Name of the Git branch the manifest wants to track for
this superproject. If not supplied the revision given by the remote element is
used if applicable, else the default element is used.
.PP
Element contactinfo
.PP
*** *Note*: This is currently a WIP. ***
.PP
This element is used to let manifest authors self\-register contact info. It has
"bugurl" as a required atrribute. This element can be repeated, and any later
entries will clobber earlier ones. This would allow manifest authors who extend
manifests to specify their own contact info.
.PP
Attribute `bugurl`: The URL to file a bug against the manifest owner.
.PP
Element include
.PP
This element provides the capability of including another manifest file into the
originating manifest. Normal rules apply for the target manifest to include \- it
must be a usable manifest on its own.
.PP
Attribute `name`: the manifest to include, specified relative to the manifest
repository's root.
.PP
"name" may not be an absolute path or use "." or ".." path components. These
restrictions are not enforced for [Local Manifests].
.PP
Attribute `groups`: List of additional groups to which all projects in the
included manifest belong. This appends and recurses, meaning all projects in
included manifests carry all parent include groups. Same syntax as the
corresponding element of `project`.
.PP
Local Manifests
.PP
Additional remotes and projects may be added through local manifest files stored
in `$TOP_DIR/.repo/local_manifests/*.xml`.
.PP
For example:
.IP
\f(CW$ ls .repo/local_manifests\fR
.IP
local_manifest.xml
another_local_manifest.xml
.IP
\f(CW$ cat .repo/local_manifests/local_manifest.xml\fR
.IP
<?xml version="1.0" encoding="UTF\-8"?>
<manifest>
.IP
<project path="manifest"
.IP
name="tools/manifest" />
.IP
<project path="platform\-manifest"
.IP
name="platform/manifest" />
.IP
</manifest>
.PP
Users may add projects to the local manifest(s) prior to a `repo sync`
invocation, instructing repo to automatically download and manage these extra
projects.
.PP
Manifest files stored in `$TOP_DIR/.repo/local_manifests/*.xml` will be loaded
in alphabetical order.
.PP
Projects from local manifest files are added into local::<local manifest
filename> group.
.PP
The legacy `$TOP_DIR/.repo/local_manifest.xml` path is no longer supported.
.SS [copyfile]: #Element\-copyfile [linkfile]: #Element\-linkfile [Local Manifests]:
.PP
#local\-manifests

52
man/repo-overview.1 Normal file
View File

@ -0,0 +1,52 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo overview" "Repo Manual"
.SH NAME
repo \- repo overview - manual page for repo overview
.SH SYNOPSIS
.B repo
\fI\,overview \/\fR[\fI\,--current-branch\/\fR] [\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Display overview of unmerged project branches
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-c\fR, \fB\-\-current\-branch\fR
consider only checked out branches
.TP
\fB\-\-no\-current\-branch\fR
consider all local branches
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help overview` to view the detailed manual.
.SH DETAILS
.PP
The 'repo overview' command is used to display an overview of the projects
branches, and list any local commits that have not yet been merged into the
project.
.PP
The \fB\-c\fR/\-\-current\-branch option can be used to restrict the output to only
branches currently checked out in each project. By default, all branches are
displayed.

41
man/repo-prune.1 Normal file
View File

@ -0,0 +1,41 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo prune" "Repo Manual"
.SH NAME
repo \- repo prune - manual page for repo prune
.SH SYNOPSIS
.B repo
\fI\,prune \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Prune (delete) already merged topics
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help prune` to view the detailed manual.

68
man/repo-rebase.1 Normal file
View File

@ -0,0 +1,68 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo rebase" "Repo Manual"
.SH NAME
repo \- repo rebase - manual page for repo rebase
.SH SYNOPSIS
.B repo
\fI\,rebase {\/\fR[\fI\,<project>\/\fR...] \fI\,| -i <project>\/\fR...\fI\,}\/\fR
.SH DESCRIPTION
Summary
.PP
Rebase local branches on upstream branch
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-\-fail\-fast\fR
stop rebasing after first error is hit
.TP
\fB\-f\fR, \fB\-\-force\-rebase\fR
pass \fB\-\-force\-rebase\fR to git rebase
.TP
\fB\-\-no\-ff\fR
pass \fB\-\-no\-ff\fR to git rebase
.TP
\fB\-\-autosquash\fR
pass \fB\-\-autosquash\fR to git rebase
.TP
\fB\-\-whitespace\fR=\fI\,WS\/\fR
pass \fB\-\-whitespace\fR to git rebase
.TP
\fB\-\-auto\-stash\fR
stash local modifications before starting
.TP
\fB\-m\fR, \fB\-\-onto\-manifest\fR
rebase onto the manifest version instead of upstream
HEAD (this helps to make sure the local tree stays
consistent if you previously synced to a manifest)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.TP
\fB\-i\fR, \fB\-\-interactive\fR
interactive rebase (single project only)
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help rebase` to view the detailed manual.
.SH DETAILS
.PP
\&'repo rebase' uses git rebase to move local changes in the current topic branch
to the HEAD of the upstream history, useful when you have made commits in a
topic branch but need to incorporate new upstream changes "underneath" them.

48
man/repo-selfupdate.1 Normal file
View File

@ -0,0 +1,48 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo selfupdate" "Repo Manual"
.SH NAME
repo \- repo selfupdate - manual page for repo selfupdate
.SH SYNOPSIS
.B repo
\fI\,selfupdate\/\fR
.SH DESCRIPTION
Summary
.PP
Update repo to the latest version
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.SS repo Version options:
.TP
\fB\-\-no\-repo\-verify\fR
do not verify repo source code
.PP
Run `repo help selfupdate` to view the detailed manual.
.SH DETAILS
.PP
The 'repo selfupdate' command upgrades repo to the latest version, if a newer
version is available.
.PP
Normally this is done automatically by 'repo sync' and does not need to be
performed by an end\-user.

136
man/repo-smartsync.1 Normal file
View File

@ -0,0 +1,136 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "August 2022" "repo smartsync" "Repo Manual"
.SH NAME
repo \- repo smartsync - manual page for repo smartsync
.SH SYNOPSIS
.B repo
\fI\,smartsync \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Update working tree to the latest known good revision
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
number of network jobs to run in parallel (defaults to
\fB\-\-jobs\fR or 1)
.TP
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
number of local checkout jobs to run in parallel
(defaults to \fB\-\-jobs\fR or 8)
.TP
\fB\-f\fR, \fB\-\-force\-broken\fR
obsolete option (to be deleted in the future)
.TP
\fB\-\-fail\-fast\fR
stop syncing after first error is hit
.TP
\fB\-\-force\-sync\fR
overwrite an existing git directory if it needs to
point to a different object directory. WARNING: this
may cause loss of data
.TP
\fB\-\-force\-remove\-dirty\fR
force remove projects with uncommitted modifications
if projects no longer exist in the manifest. WARNING:
this may cause loss of data
.TP
\fB\-l\fR, \fB\-\-local\-only\fR
only update working tree, don't fetch
.TP
\fB\-\-no\-manifest\-update\fR, \fB\-\-nmu\fR
use the existing manifest checkout as\-is. (do not
update to the latest revision)
.TP
\fB\-n\fR, \fB\-\-network\-only\fR
fetch only, don't update working tree
.TP
\fB\-d\fR, \fB\-\-detach\fR
detach projects back to manifest revision
.TP
\fB\-c\fR, \fB\-\-current\-branch\fR
fetch only current branch from server
.TP
\fB\-\-no\-current\-branch\fR
fetch all branches from server
.TP
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
temporary manifest to use for this sync
.TP
\fB\-\-clone\-bundle\fR
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
.TP
\fB\-\-no\-clone\-bundle\fR
disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
.TP
\fB\-u\fR MANIFEST_SERVER_USERNAME, \fB\-\-manifest\-server\-username\fR=\fI\,MANIFEST_SERVER_USERNAME\/\fR
username to authenticate with the manifest server
.TP
\fB\-p\fR MANIFEST_SERVER_PASSWORD, \fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
password to authenticate with the manifest server
.TP
\fB\-\-fetch\-submodules\fR
fetch submodules from server
.TP
\fB\-\-use\-superproject\fR
use the manifest superproject to sync projects;
implies \fB\-c\fR
.TP
\fB\-\-no\-use\-superproject\fR
disable use of manifest superprojects
.TP
\fB\-\-tags\fR
fetch tags
.TP
\fB\-\-no\-tags\fR
don't fetch tags (default)
.TP
\fB\-\-optimized\-fetch\fR
only fetch projects fixed to sha1 if revision does not
exist locally
.TP
\fB\-\-retry\-fetches\fR=\fI\,RETRY_FETCHES\/\fR
number of times to retry fetches on transient errors
.TP
\fB\-\-prune\fR
delete refs that no longer exist on the remote
(default)
.TP
\fB\-\-no\-prune\fR
do not delete refs that no longer exist on the remote
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.SS repo Version options:
.TP
\fB\-\-no\-repo\-verify\fR
do not verify repo source code
.PP
Run `repo help smartsync` to view the detailed manual.
.SH DETAILS
.PP
The 'repo smartsync' command is a shortcut for sync \fB\-s\fR.

43
man/repo-stage.1 Normal file
View File

@ -0,0 +1,43 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo stage" "Repo Manual"
.SH NAME
repo \- repo stage - manual page for repo stage
.SH SYNOPSIS
.B repo
\fI\,stage -i \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Stage file(s) for commit
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.TP
\fB\-i\fR, \fB\-\-interactive\fR
use interactive staging
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help stage` to view the detailed manual.
.SH DETAILS
.PP
The 'repo stage' command stages files to prepare the next commit.

54
man/repo-start.1 Normal file
View File

@ -0,0 +1,54 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo start" "Repo Manual"
.SH NAME
repo \- repo start - manual page for repo start
.SH SYNOPSIS
.B repo
\fI\,start <newbranchname> \/\fR[\fI\,--all | <project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Start a new branch for development
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-\-all\fR
begin branch in all projects
.TP
\fB\-r\fR REVISION, \fB\-\-rev\fR=\fI\,REVISION\/\fR, \fB\-\-revision\fR=\fI\,REVISION\/\fR
point branch at this revision instead of upstream
.TP
\fB\-\-head\fR, \fB\-\-HEAD\fR
abbreviation for \fB\-\-rev\fR HEAD
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help start` to view the detailed manual.
.SH DETAILS
.PP
\&'repo start' begins a new branch of development, starting from the revision
specified in the manifest.

111
man/repo-status.1 Normal file
View File

@ -0,0 +1,111 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo status" "Repo Manual"
.SH NAME
repo \- repo status - manual page for repo status
.SH SYNOPSIS
.B repo
\fI\,status \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Show the working tree status
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-o\fR, \fB\-\-orphans\fR
include objects in working directory outside of repo
projects
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help status` to view the detailed manual.
.SH DETAILS
.PP
\&'repo status' compares the working tree to the staging area (aka index), and the
most recent commit on this branch (HEAD), in each project specified. A summary
is displayed, one line per file where there is a difference between these three
states.
.PP
The \fB\-j\fR/\-\-jobs option can be used to run multiple status queries in parallel.
.PP
The \fB\-o\fR/\-\-orphans option can be used to show objects that are in the working
directory, but not associated with a repo project. This includes unmanaged
top\-level files and directories, but also includes deeper items. For example, if
dir/subdir/proj1 and dir/subdir/proj2 are repo projects, dir/subdir/proj3 will
be shown if it is not known to repo.
.PP
Status Display
.PP
The status display is organized into three columns of information, for example
if the file 'subcmds/status.py' is modified in the project 'repo' on branch
\&'devwork':
.TP
project repo/
branch devwork
.TP
\fB\-m\fR
subcmds/status.py
.PP
The first column explains how the staging area (index) differs from the last
commit (HEAD). Its values are always displayed in upper case and have the
following meanings:
.TP
\-:
no difference
.TP
A:
added (not in HEAD, in index )
.TP
M:
modified ( in HEAD, in index, different content )
.TP
D:
deleted ( in HEAD, not in index )
.TP
R:
renamed (not in HEAD, in index, path changed )
.TP
C:
copied (not in HEAD, in index, copied from another)
.TP
T:
mode changed ( in HEAD, in index, same content )
.TP
U:
unmerged; conflict resolution required
.PP
The second column explains how the working directory differs from the index. Its
values are always displayed in lower case and have the following meanings:
.TP
\-:
new / unknown (not in index, in work tree )
.TP
m:
modified ( in index, in work tree, modified )
.TP
d:
deleted ( in index, not in work tree )

227
man/repo-sync.1 Normal file
View File

@ -0,0 +1,227 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "August 2022" "repo sync" "Repo Manual"
.SH NAME
repo \- repo sync - manual page for repo sync
.SH SYNOPSIS
.B repo
\fI\,sync \/\fR[\fI\,<project>\/\fR...]
.SH DESCRIPTION
Summary
.PP
Update working tree to the latest revision
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
number of network jobs to run in parallel (defaults to
\fB\-\-jobs\fR or 1)
.TP
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
number of local checkout jobs to run in parallel
(defaults to \fB\-\-jobs\fR or 8)
.TP
\fB\-f\fR, \fB\-\-force\-broken\fR
obsolete option (to be deleted in the future)
.TP
\fB\-\-fail\-fast\fR
stop syncing after first error is hit
.TP
\fB\-\-force\-sync\fR
overwrite an existing git directory if it needs to
point to a different object directory. WARNING: this
may cause loss of data
.TP
\fB\-\-force\-remove\-dirty\fR
force remove projects with uncommitted modifications
if projects no longer exist in the manifest. WARNING:
this may cause loss of data
.TP
\fB\-l\fR, \fB\-\-local\-only\fR
only update working tree, don't fetch
.TP
\fB\-\-no\-manifest\-update\fR, \fB\-\-nmu\fR
use the existing manifest checkout as\-is. (do not
update to the latest revision)
.TP
\fB\-n\fR, \fB\-\-network\-only\fR
fetch only, don't update working tree
.TP
\fB\-d\fR, \fB\-\-detach\fR
detach projects back to manifest revision
.TP
\fB\-c\fR, \fB\-\-current\-branch\fR
fetch only current branch from server
.TP
\fB\-\-no\-current\-branch\fR
fetch all branches from server
.TP
\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
temporary manifest to use for this sync
.TP
\fB\-\-clone\-bundle\fR
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
.TP
\fB\-\-no\-clone\-bundle\fR
disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS
.TP
\fB\-u\fR MANIFEST_SERVER_USERNAME, \fB\-\-manifest\-server\-username\fR=\fI\,MANIFEST_SERVER_USERNAME\/\fR
username to authenticate with the manifest server
.TP
\fB\-p\fR MANIFEST_SERVER_PASSWORD, \fB\-\-manifest\-server\-password\fR=\fI\,MANIFEST_SERVER_PASSWORD\/\fR
password to authenticate with the manifest server
.TP
\fB\-\-fetch\-submodules\fR
fetch submodules from server
.TP
\fB\-\-use\-superproject\fR
use the manifest superproject to sync projects;
implies \fB\-c\fR
.TP
\fB\-\-no\-use\-superproject\fR
disable use of manifest superprojects
.TP
\fB\-\-tags\fR
fetch tags
.TP
\fB\-\-no\-tags\fR
don't fetch tags (default)
.TP
\fB\-\-optimized\-fetch\fR
only fetch projects fixed to sha1 if revision does not
exist locally
.TP
\fB\-\-retry\-fetches\fR=\fI\,RETRY_FETCHES\/\fR
number of times to retry fetches on transient errors
.TP
\fB\-\-prune\fR
delete refs that no longer exist on the remote
(default)
.TP
\fB\-\-no\-prune\fR
do not delete refs that no longer exist on the remote
.TP
\fB\-s\fR, \fB\-\-smart\-sync\fR
smart sync using manifest from the latest known good
build
.TP
\fB\-t\fR SMART_TAG, \fB\-\-smart\-tag\fR=\fI\,SMART_TAG\/\fR
smart sync using manifest from a known tag
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.SS repo Version options:
.TP
\fB\-\-no\-repo\-verify\fR
do not verify repo source code
.PP
Run `repo help sync` to view the detailed manual.
.SH DETAILS
.PP
The 'repo sync' command synchronizes local project directories with the remote
repositories specified in the manifest. If a local project does not yet exist,
it will clone a new local directory from the remote repository and set up
tracking branches as specified in the manifest. If the local project already
exists, 'repo sync' will update the remote branches and rebase any new local
changes on top of the new remote changes.
.PP
\&'repo sync' will synchronize all projects listed at the command line. Projects
can be specified either by name, or by a relative or absolute path to the
project's local directory. If no projects are specified, 'repo sync' will
synchronize all projects listed in the manifest.
.PP
The \fB\-d\fR/\-\-detach option can be used to switch specified projects back to the
manifest revision. This option is especially helpful if the project is currently
on a topic branch, but the manifest revision is temporarily needed.
.PP
The \fB\-s\fR/\-\-smart\-sync option can be used to sync to a known good build as
specified by the manifest\-server element in the current manifest. The
\fB\-t\fR/\-\-smart\-tag option is similar and allows you to specify a custom tag/label.
.PP
The \fB\-u\fR/\-\-manifest\-server\-username and \fB\-p\fR/\-\-manifest\-server\-password options can
be used to specify a username and password to authenticate with the manifest
server when using the \fB\-s\fR or \fB\-t\fR option.
.PP
If \fB\-u\fR and \fB\-p\fR are not specified when using the \fB\-s\fR or \fB\-t\fR option, 'repo sync' will
attempt to read authentication credentials for the manifest server from the
user's .netrc file.
.PP
\&'repo sync' will not use authentication credentials from \fB\-u\fR/\-p or .netrc if the
manifest server specified in the manifest file already includes credentials.
.PP
By default, all projects will be synced. The \fB\-\-fail\-fast\fR option can be used to
halt syncing as soon as possible when the first project fails to sync.
.PP
The \fB\-\-force\-sync\fR option can be used to overwrite existing git directories if
they have previously been linked to a different object directory. WARNING: This
may cause data to be lost since refs may be removed when overwriting.
.PP
The \fB\-\-force\-remove\-dirty\fR option can be used to remove previously used projects
with uncommitted changes. WARNING: This may cause data to be lost since
uncommitted changes may be removed with projects that no longer exist in the
manifest.
.PP
The \fB\-\-no\-clone\-bundle\fR option disables any attempt to use \fI\,$URL/clone.bundle\/\fP to
bootstrap a new Git repository from a resumeable bundle file on a content
delivery network. This may be necessary if there are problems with the local
Python HTTP client or proxy configuration, but the Git binary works.
.PP
The \fB\-\-fetch\-submodules\fR option enables fetching Git submodules of a project from
server.
.PP
The \fB\-c\fR/\-\-current\-branch option can be used to only fetch objects that are on the
branch specified by a project's revision.
.PP
The \fB\-\-optimized\-fetch\fR option can be used to only fetch projects that are fixed
to a sha1 revision if the sha1 revision does not already exist locally.
.PP
The \fB\-\-prune\fR option can be used to remove any refs that no longer exist on the
remote.
.PP
SSH Connections
.PP
If at least one project remote URL uses an SSH connection (ssh://, git+ssh://,
or user@host:path syntax) repo will automatically enable the SSH ControlMaster
option when connecting to that host. This feature permits other projects in the
same 'repo sync' session to reuse the same SSH tunnel, saving connection setup
overheads.
.PP
To disable this behavior on UNIX platforms, set the GIT_SSH environment variable
to 'ssh'. For example:
.IP
export GIT_SSH=ssh
repo sync
.PP
Compatibility
.PP
This feature is automatically disabled on Windows, due to the lack of UNIX
domain socket support.
.PP
This feature is not compatible with url.insteadof rewrites in the user's
~/.gitconfig. 'repo sync' is currently not able to perform the rewrite early
enough to establish the ControlMaster tunnel.
.PP
If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or later is
required to fix a server side protocol bug.

203
man/repo-upload.1 Normal file
View File

@ -0,0 +1,203 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "August 2022" "repo upload" "Repo Manual"
.SH NAME
repo \- repo upload - manual page for repo upload
.SH SYNOPSIS
.B repo
\fI\,upload \/\fR[\fI\,--re --cc\/\fR] [\fI\,<project>\/\fR]...
.SH DESCRIPTION
Summary
.PP
Upload changes for code review
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-j\fR JOBS, \fB\-\-jobs\fR=\fI\,JOBS\/\fR
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
\fB\-t\fR
send local branch name to Gerrit Code Review
.TP
\fB\-\-hashtag\fR=\fI\,HASHTAGS\/\fR, \fB\-\-ht\fR=\fI\,HASHTAGS\/\fR
add hashtags (comma delimited) to the review
.TP
\fB\-\-hashtag\-branch\fR, \fB\-\-htb\fR
add local branch name as a hashtag
.TP
\fB\-l\fR LABELS, \fB\-\-label\fR=\fI\,LABELS\/\fR
add a label when uploading
.TP
\fB\-\-re\fR=\fI\,REVIEWERS\/\fR, \fB\-\-reviewers\fR=\fI\,REVIEWERS\/\fR
request reviews from these people
.TP
\fB\-\-cc\fR=\fI\,CC\/\fR
also send email to these email addresses
.TP
\fB\-\-br\fR=\fI\,BRANCH\/\fR, \fB\-\-branch\fR=\fI\,BRANCH\/\fR
(local) branch to upload
.TP
\fB\-c\fR, \fB\-\-current\-branch\fR
upload current git branch
.TP
\fB\-\-no\-current\-branch\fR
upload all git branches
.TP
\fB\-\-ne\fR, \fB\-\-no\-emails\fR
do not send e\-mails on upload
.TP
\fB\-p\fR, \fB\-\-private\fR
upload as a private change (deprecated; use \fB\-\-wip\fR)
.TP
\fB\-w\fR, \fB\-\-wip\fR
upload as a work\-in\-progress change
.TP
\fB\-r\fR, \fB\-\-ready\fR
mark change as ready (clears work\-in\-progress setting)
.TP
\fB\-o\fR PUSH_OPTIONS, \fB\-\-push\-option\fR=\fI\,PUSH_OPTIONS\/\fR
additional push options to transmit
.TP
\fB\-D\fR BRANCH, \fB\-\-destination\fR=\fI\,BRANCH\/\fR, \fB\-\-dest\fR=\fI\,BRANCH\/\fR
submit for review on this target branch
.TP
\fB\-n\fR, \fB\-\-dry\-run\fR
do everything except actually upload the CL
.TP
\fB\-y\fR, \fB\-\-yes\fR
answer yes to all safe prompts
.TP
\fB\-\-ignore\-untracked\-files\fR
ignore untracked files in the working copy
.TP
\fB\-\-no\-ignore\-untracked\-files\fR
always ask about untracked files in the working copy
.TP
\fB\-\-no\-cert\-checks\fR
disable verifying ssl certs (unsafe)
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.SS pre\-upload hooks:
.TP
\fB\-\-no\-verify\fR
Do not run the pre\-upload hook.
.TP
\fB\-\-verify\fR
Run the pre\-upload hook without prompting.
.TP
\fB\-\-ignore\-hooks\fR
Do not abort if pre\-upload hooks fail.
.PP
Run `repo help upload` to view the detailed manual.
.SH DETAILS
.PP
The 'repo upload' command is used to send changes to the Gerrit Code Review
system. It searches for topic branches in local projects that have not yet been
published for review. If multiple topic branches are found, 'repo upload' opens
an editor to allow the user to select which branches to upload.
.PP
\&'repo upload' searches for uploadable changes in all projects listed at the
command line. Projects can be specified either by name, or by a relative or
absolute path to the project's local directory. If no projects are specified,
\&'repo upload' will search for uploadable changes in all projects listed in the
manifest.
.PP
If the \fB\-\-reviewers\fR or \fB\-\-cc\fR options are passed, those emails are added to the
respective list of users, and emails are sent to any new users. Users passed as
\fB\-\-reviewers\fR must already be registered with the code review system, or the
upload will fail.
.PP
While most normal Gerrit options have dedicated command line options, direct
access to the Gerit options is available via \fB\-\-push\-options\fR. This is useful when
Gerrit has newer functionality that repo upload doesn't yet support, or doesn't
have plans to support. See the Push Options documentation for more details:
https://gerrit\-review.googlesource.com/Documentation/user\-upload.html#push_options
.PP
Configuration
.PP
review.URL.autoupload:
.PP
To disable the "Upload ... (y/N)?" prompt, you can set a per\-project or global
Git configuration option. If review.URL.autoupload is set to "true" then repo
will assume you always answer "y" at the prompt, and will not prompt you
further. If it is set to "false" then repo will assume you always answer "n",
and will abort.
.PP
review.URL.autoreviewer:
.PP
To automatically append a user or mailing list to reviews, you can set a
per\-project or global Git option to do so.
.PP
review.URL.autocopy:
.PP
To automatically copy a user or mailing list to all uploaded reviews, you can
set a per\-project or global Git option to do so. Specifically,
review.URL.autocopy can be set to a comma separated list of reviewers who you
always want copied on all uploads with a non\-empty \fB\-\-re\fR argument.
.PP
review.URL.username:
.PP
Override the username used to connect to Gerrit Code Review. By default the
local part of the email address is used.
.PP
The URL must match the review URL listed in the manifest XML file, or in the
\&.git/config within the project. For example:
.IP
[remote "origin"]
.IP
url = git://git.example.com/project.git
review = http://review.example.com/
.IP
[review "http://review.example.com/"]
.IP
autoupload = true
autocopy = johndoe@company.com,my\-team\-alias@company.com
.PP
review.URL.uploadtopic:
.PP
To add a topic branch whenever uploading a commit, you can set a per\-project or
global Git option to do so. If review.URL.uploadtopic is set to "true" then repo
will assume you always want the equivalent of the \fB\-t\fR option to the repo command.
If unset or set to "false" then repo will make use of only the command line
option.
.PP
review.URL.uploadhashtags:
.PP
To add hashtags whenever uploading a commit, you can set a per\-project or global
Git option to do so. The value of review.URL.uploadhashtags will be used as
comma delimited hashtags like the \fB\-\-hashtag\fR option.
.PP
review.URL.uploadlabels:
.PP
To add labels whenever uploading a commit, you can set a per\-project or global
Git option to do so. The value of review.URL.uploadlabels will be used as comma
delimited labels like the \fB\-\-label\fR option.
.PP
review.URL.uploadnotify:
.PP
Control e\-mail notifications when uploading.
https://gerrit\-review.googlesource.com/Documentation/user\-upload.html#notify
.PP
References
.PP
Gerrit Code Review: https://www.gerritcodereview.com/

37
man/repo-version.1 Normal file
View File

@ -0,0 +1,37 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo version" "Repo Manual"
.SH NAME
repo \- repo version - manual page for repo version
.SH SYNOPSIS
.B repo
\fI\,version\/\fR
.SH DESCRIPTION
Summary
.PP
Display the version of repo
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.SS Logging options:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
show all output
.TP
\fB\-q\fR, \fB\-\-quiet\fR
only show errors
.SS Multi\-manifest options:
.TP
\fB\-\-outer\-manifest\fR
operate starting at the outermost manifest
.TP
\fB\-\-no\-outer\-manifest\fR
do not operate on outer manifests
.TP
\fB\-\-this\-manifest\-only\fR
only operate on this (sub)manifest
.TP
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
operate on this manifest and its submanifests
.PP
Run `repo help version` to view the detailed manual.

136
man/repo.1 Normal file
View File

@ -0,0 +1,136 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2022" "repo" "Repo Manual"
.SH NAME
repo \- repository management tool built on top of git
.SH SYNOPSIS
.B repo
[\fI\,-p|--paginate|--no-pager\/\fR] \fI\,COMMAND \/\fR[\fI\,ARGS\/\fR]
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-\-help\-all\fR
show this help message with all subcommands and exit
.TP
\fB\-p\fR, \fB\-\-paginate\fR
display command output in the pager
.TP
\fB\-\-no\-pager\fR
disable the pager
.TP
\fB\-\-color\fR=\fI\,COLOR\/\fR
control color usage: auto, always, never
.TP
\fB\-\-trace\fR
trace git command execution (REPO_TRACE=1)
.TP
\fB\-\-trace\-python\fR
trace python command execution
.TP
\fB\-\-time\fR
time repo command execution
.TP
\fB\-\-version\fR
display this version of repo
.TP
\fB\-\-show\-toplevel\fR
display the path of the top\-level directory of the
repo client checkout
.TP
\fB\-\-event\-log\fR=\fI\,EVENT_LOG\/\fR
filename of event log to append timeline to
.TP
\fB\-\-git\-trace2\-event\-log\fR=\fI\,GIT_TRACE2_EVENT_LOG\/\fR
directory to write git trace2 event log to
.TP
\fB\-\-submanifest\-path\fR=\fI\,REL_PATH\/\fR
submanifest path
.SS "The complete list of recognized repo commands is:"
.TP
abandon
Permanently abandon a development branch
.TP
branch
View current topic branches
.TP
branches
View current topic branches
.TP
checkout
Checkout a branch for development
.TP
cherry\-pick
Cherry\-pick a change.
.TP
diff
Show changes between commit and working tree
.TP
diffmanifests
Manifest diff utility
.TP
download
Download and checkout a change
.TP
forall
Run a shell command in each project
.TP
gitc\-delete
Delete a GITC Client.
.TP
gitc\-init
Initialize a GITC Client.
.TP
grep
Print lines matching a pattern
.TP
help
Display detailed help on a command
.TP
info
Get info on the manifest branch, current branch or unmerged branches
.TP
init
Initialize a repo client checkout in the current directory
.TP
list
List projects and their associated directories
.TP
manifest
Manifest inspection utility
.TP
overview
Display overview of unmerged project branches
.TP
prune
Prune (delete) already merged topics
.TP
rebase
Rebase local branches on upstream branch
.TP
selfupdate
Update repo to the latest version
.TP
smartsync
Update working tree to the latest known good revision
.TP
stage
Stage file(s) for commit
.TP
start
Start a new branch for development
.TP
status
Show the working tree status
.TP
sync
Update working tree to the latest revision
.TP
upload
Upload changes for code review
.TP
version
Display the version of repo
.PP
See 'repo help <command>' for more information on a specific command.
Bug reports: https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue

File diff suppressed because it is too large Load Diff

50
pager.py Executable file → Normal file
View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,22 +12,63 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import select
import subprocess
import sys
import platform_utils
active = False
pager_process = None
old_stdout = None
old_stderr = None
def RunPager(globalConfig):
global active
if not os.isatty(0) or not os.isatty(1):
return
pager = _SelectPager(globalConfig)
if pager == '' or pager == 'cat':
return
if platform_utils.isWindows():
_PipePager(pager)
else:
_ForkPager(pager)
def TerminatePager():
global pager_process, old_stdout, old_stderr
if pager_process:
sys.stdout.flush()
sys.stderr.flush()
pager_process.stdin.close()
pager_process.wait()
pager_process = None
# Restore initial stdout/err in case there is more output in this process
# after shutting down the pager process
sys.stdout = old_stdout
sys.stderr = old_stderr
def _PipePager(pager):
global pager_process, old_stdout, old_stderr
assert pager_process is None, "Only one active pager process at a time"
# Create pager process, piping stdout/err into its stdin
try:
pager_process = subprocess.Popen([pager], stdin=subprocess.PIPE, stdout=sys.stdout,
stderr=sys.stderr)
except FileNotFoundError:
sys.exit(f'fatal: cannot start pager "{pager}"')
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = pager_process.stdin
sys.stderr = pager_process.stdin
def _ForkPager(pager):
global active
# This process turns into the pager; a child it forks will
# do the real processing and output back to the pager. This
# is necessary to keep the pager in control of the tty.
@ -53,6 +93,7 @@ def RunPager(globalConfig):
print("fatal: cannot start pager '%s'" % pager, file=sys.stderr)
sys.exit(255)
def _SelectPager(globalConfig):
try:
return os.environ['GIT_PAGER']
@ -70,6 +111,7 @@ def _SelectPager(globalConfig):
return 'less'
def _BecomePager(pager):
# Delaying execution of the pager until we have output
# ready works around a long-standing bug in popularly

265
platform_utils.py Normal file
View File

@ -0,0 +1,265 @@
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
import os
import platform
import shutil
import stat
def isWindows():
""" Returns True when running with the native port of Python for Windows,
False when running on any other platform (including the Cygwin port of
Python).
"""
# Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
return platform.system() == "Windows"
def symlink(source, link_name):
"""Creates a symbolic link pointing to source named link_name.
Note: On Windows, source must exist on disk, as the implementation needs
to know whether to create a "File" or a "Directory" symbolic link.
"""
if isWindows():
import platform_utils_win32
source = _validate_winpath(source)
link_name = _validate_winpath(link_name)
target = os.path.join(os.path.dirname(link_name), source)
if isdir(target):
platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name)
else:
platform_utils_win32.create_filesymlink(_makelongpath(source), link_name)
else:
return os.symlink(source, link_name)
def _validate_winpath(path):
path = os.path.normpath(path)
if _winpath_is_valid(path):
return path
raise ValueError("Path \"%s\" must be a relative path or an absolute "
"path starting with a drive letter".format(path))
def _winpath_is_valid(path):
"""Windows only: returns True if path is relative (e.g. ".\\foo") or is
absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
is ambiguous (e.g. "x:foo" or "\\foo").
"""
assert isWindows()
path = os.path.normpath(path)
drive, tail = os.path.splitdrive(path)
if tail:
if not drive:
return tail[0] != os.sep # "\\foo" is invalid
else:
return tail[0] == os.sep # "x:foo" is invalid
else:
return not drive # "x:" is invalid
def _makelongpath(path):
"""Return the input path normalized to support the Windows long path syntax
("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
MAX_PATH limit.
"""
if isWindows():
# Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246.
if len(path) < 246:
return path
if path.startswith(u"\\\\?\\"):
return path
if not os.path.isabs(path):
return path
# Append prefix and ensure unicode so that the special longpath syntax
# is supported by underlying Win32 API calls
return u"\\\\?\\" + os.path.normpath(path)
else:
return path
def rmtree(path, ignore_errors=False):
"""shutil.rmtree(path) wrapper with support for long paths on Windows.
Availability: Unix, Windows."""
onerror = None
if isWindows():
path = _makelongpath(path)
onerror = handle_rmtree_error
shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
def handle_rmtree_error(function, path, excinfo):
# Allow deleting read-only files
os.chmod(path, stat.S_IWRITE)
function(path)
def rename(src, dst):
"""os.rename(src, dst) wrapper with support for long paths on Windows.
Availability: Unix, Windows."""
if isWindows():
# On Windows, rename fails if destination exists, see
# https://docs.python.org/2/library/os.html#os.rename
try:
os.rename(_makelongpath(src), _makelongpath(dst))
except OSError as e:
if e.errno == errno.EEXIST:
os.remove(_makelongpath(dst))
os.rename(_makelongpath(src), _makelongpath(dst))
else:
raise
else:
shutil.move(src, dst)
def remove(path, missing_ok=False):
"""Remove (delete) the file path. This is a replacement for os.remove that
allows deleting read-only files on Windows, with support for long paths and
for deleting directory symbolic links.
Availability: Unix, Windows."""
longpath = _makelongpath(path) if isWindows() else path
try:
os.remove(longpath)
except OSError as e:
if e.errno == errno.EACCES:
os.chmod(longpath, stat.S_IWRITE)
# Directory symbolic links must be deleted with 'rmdir'.
if islink(longpath) and isdir(longpath):
os.rmdir(longpath)
else:
os.remove(longpath)
elif missing_ok and e.errno == errno.ENOENT:
pass
else:
raise
def walk(top, topdown=True, onerror=None, followlinks=False):
"""os.walk(path) wrapper with support for long paths on Windows.
Availability: Windows, Unix.
"""
if isWindows():
return _walk_windows_impl(top, topdown, onerror, followlinks)
else:
return os.walk(top, topdown, onerror, followlinks)
def _walk_windows_impl(top, topdown, onerror, followlinks):
try:
names = listdir(top)
except Exception as err:
if onerror is not None:
onerror(err)
return
dirs, nondirs = [], []
for name in names:
if isdir(os.path.join(top, name)):
dirs.append(name)
else:
nondirs.append(name)
if topdown:
yield top, dirs, nondirs
for name in dirs:
new_path = os.path.join(top, name)
if followlinks or not islink(new_path):
for x in _walk_windows_impl(new_path, topdown, onerror, followlinks):
yield x
if not topdown:
yield top, dirs, nondirs
def listdir(path):
"""os.listdir(path) wrapper with support for long paths on Windows.
Availability: Windows, Unix.
"""
return os.listdir(_makelongpath(path))
def rmdir(path):
"""os.rmdir(path) wrapper with support for long paths on Windows.
Availability: Windows, Unix.
"""
os.rmdir(_makelongpath(path))
def isdir(path):
"""os.path.isdir(path) wrapper with support for long paths on Windows.
Availability: Windows, Unix.
"""
return os.path.isdir(_makelongpath(path))
def islink(path):
"""os.path.islink(path) wrapper with support for long paths on Windows.
Availability: Windows, Unix.
"""
if isWindows():
import platform_utils_win32
return platform_utils_win32.islink(_makelongpath(path))
else:
return os.path.islink(path)
def readlink(path):
"""Return a string representing the path to which the symbolic link
points. The result may be either an absolute or relative pathname;
if it is relative, it may be converted to an absolute pathname using
os.path.join(os.path.dirname(path), result).
Availability: Windows, Unix.
"""
if isWindows():
import platform_utils_win32
return platform_utils_win32.readlink(_makelongpath(path))
else:
return os.readlink(path)
def realpath(path):
"""Return the canonical path of the specified filename, eliminating
any symbolic links encountered in the path.
Availability: Windows, Unix.
"""
if isWindows():
current_path = os.path.abspath(path)
path_tail = []
for c in range(0, 100): # Avoid cycles
if islink(current_path):
target = readlink(current_path)
current_path = os.path.join(os.path.dirname(current_path), target)
else:
basename = os.path.basename(current_path)
if basename == '':
path_tail.append(current_path)
break
path_tail.append(basename)
current_path = os.path.dirname(current_path)
path_tail.reverse()
result = os.path.normpath(os.path.join(*path_tail))
return result
else:
return os.path.realpath(path)

209
platform_utils_win32.py Normal file
View File

@ -0,0 +1,209 @@
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
from ctypes import c_buffer, c_ubyte, Structure, Union, byref
from ctypes.wintypes import BOOL, BOOLEAN, LPCWSTR, DWORD, HANDLE
from ctypes.wintypes import WCHAR, USHORT, LPVOID, ULONG, LPDWORD
kernel32 = WinDLL('kernel32', use_last_error=True)
UCHAR = c_ubyte
# Win32 error codes
ERROR_SUCCESS = 0
ERROR_NOT_SUPPORTED = 50
ERROR_PRIVILEGE_NOT_HELD = 1314
# Win32 API entry points
CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
CreateSymbolicLinkW.restype = BOOLEAN
CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
LPCWSTR, # lpTargetFileName In
DWORD) # dwFlags In
# Symbolic link creation flags
SYMBOLIC_LINK_FLAG_FILE = 0x00
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
# symlink support for CreateSymbolicLink() starting with Windows 10 (1703, v10.0.14972)
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02
GetFileAttributesW = kernel32.GetFileAttributesW
GetFileAttributesW.restype = DWORD
GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
CreateFileW = kernel32.CreateFileW
CreateFileW.restype = HANDLE
CreateFileW.argtypes = (LPCWSTR, # lpFileName In
DWORD, # dwDesiredAccess In
DWORD, # dwShareMode In
LPVOID, # lpSecurityAttributes In_opt
DWORD, # dwCreationDisposition In
DWORD, # dwFlagsAndAttributes In
HANDLE) # hTemplateFile In_opt
CloseHandle = kernel32.CloseHandle
CloseHandle.restype = BOOL
CloseHandle.argtypes = (HANDLE,) # hObject In
INVALID_HANDLE_VALUE = HANDLE(-1).value
OPEN_EXISTING = 3
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
DeviceIoControl = kernel32.DeviceIoControl
DeviceIoControl.restype = BOOL
DeviceIoControl.argtypes = (HANDLE, # hDevice In
DWORD, # dwIoControlCode In
LPVOID, # lpInBuffer In_opt
DWORD, # nInBufferSize In
LPVOID, # lpOutBuffer Out_opt
DWORD, # nOutBufferSize In
LPDWORD, # lpBytesReturned Out_opt
LPVOID) # lpOverlapped Inout_opt
# Device I/O control flags and options
FSCTL_GET_REPARSE_POINT = 0x000900A8
IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
IO_REPARSE_TAG_SYMLINK = 0xA000000C
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
class GENERIC_REPARSE_BUFFER(Structure):
_fields_ = (('DataBuffer', UCHAR * 1),)
class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
_fields_ = (('SubstituteNameOffset', USHORT),
('SubstituteNameLength', USHORT),
('PrintNameOffset', USHORT),
('PrintNameLength', USHORT),
('Flags', ULONG),
('PathBuffer', WCHAR * 1))
@property
def PrintName(self):
arrayt = WCHAR * (self.PrintNameLength // 2)
offset = type(self).PathBuffer.offset + self.PrintNameOffset
return arrayt.from_address(addressof(self) + offset).value
class MOUNT_POINT_REPARSE_BUFFER(Structure):
_fields_ = (('SubstituteNameOffset', USHORT),
('SubstituteNameLength', USHORT),
('PrintNameOffset', USHORT),
('PrintNameLength', USHORT),
('PathBuffer', WCHAR * 1))
@property
def PrintName(self):
arrayt = WCHAR * (self.PrintNameLength // 2)
offset = type(self).PathBuffer.offset + self.PrintNameOffset
return arrayt.from_address(addressof(self) + offset).value
class REPARSE_DATA_BUFFER(Structure):
class REPARSE_BUFFER(Union):
_fields_ = (('SymbolicLinkReparseBuffer', SYMBOLIC_LINK_REPARSE_BUFFER),
('MountPointReparseBuffer', MOUNT_POINT_REPARSE_BUFFER),
('GenericReparseBuffer', GENERIC_REPARSE_BUFFER))
_fields_ = (('ReparseTag', ULONG),
('ReparseDataLength', USHORT),
('Reserved', USHORT),
('ReparseBuffer', REPARSE_BUFFER))
_anonymous_ = ('ReparseBuffer',)
def create_filesymlink(source, link_name):
"""Creates a Windows file symbolic link source pointing to link_name."""
_create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE)
def create_dirsymlink(source, link_name):
"""Creates a Windows directory symbolic link source pointing to link_name.
"""
_create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY)
def _create_symlink(source, link_name, dwFlags):
if not CreateSymbolicLinkW(link_name, source,
dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE):
# See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
# "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
# retry without it."
if not CreateSymbolicLinkW(link_name, source, dwFlags):
code = get_last_error()
error_desc = FormatError(code).strip()
if code == ERROR_PRIVILEGE_NOT_HELD:
raise OSError(errno.EPERM, error_desc, link_name)
_raise_winerror(
code,
'Error creating symbolic link \"%s\"'.format(link_name))
def islink(path):
result = GetFileAttributesW(path)
if result == INVALID_FILE_ATTRIBUTES:
return False
return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
def readlink(path):
reparse_point_handle = CreateFileW(path,
0,
0,
None,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT |
FILE_FLAG_BACKUP_SEMANTICS,
None)
if reparse_point_handle == INVALID_HANDLE_VALUE:
_raise_winerror(
get_last_error(),
'Error opening symbolic link \"%s\"'.format(path))
target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
n_bytes_returned = DWORD()
io_result = DeviceIoControl(reparse_point_handle,
FSCTL_GET_REPARSE_POINT,
None,
0,
target_buffer,
len(target_buffer),
byref(n_bytes_returned),
None)
CloseHandle(reparse_point_handle)
if not io_result:
_raise_winerror(
get_last_error(),
'Error reading symbolic link \"%s\"'.format(path))
rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
return rdb.SymbolicLinkReparseBuffer.PrintName
elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
return rdb.MountPointReparseBuffer.PrintName
# Unsupported reparse point type
_raise_winerror(
ERROR_NOT_SUPPORTED,
'Error reading symbolic link \"%s\"'.format(path))
def _raise_winerror(code, error_desc):
win_error_desc = FormatError(code).strip()
error_desc = "%s: %s".format(error_desc, win_error_desc)
raise WinError(code, error_desc)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -16,21 +15,69 @@
import os
import sys
from time import time
from trace import IsTrace
from repo_trace import IsTrace
_NOT_TTY = not os.isatty(2)
# This will erase all content in the current line (wherever the cursor is).
# It does not move the cursor, so this is usually followed by \r to move to
# column 0.
CSI_ERASE_LINE = '\x1b[2K'
# This will erase all content in the current line after the cursor. This is
# useful for partial updates & progress messages as the terminal can display
# it better.
CSI_ERASE_LINE_AFTER = '\x1b[K'
def duration_str(total):
"""A less noisy timedelta.__str__.
The default timedelta stringification contains a lot of leading zeros and
uses microsecond resolution. This makes for noisy output.
"""
hours, rem = divmod(total, 3600)
mins, secs = divmod(rem, 60)
ret = '%.3fs' % (secs,)
if mins:
ret = '%im%s' % (mins, ret)
if hours:
ret = '%ih%s' % (hours, ret)
return ret
class Progress(object):
def __init__(self, title, total=0, units=''):
def __init__(self, title, total=0, units='', print_newline=False, delay=True,
quiet=False):
self._title = title
self._total = total
self._done = 0
self._lastp = -1
self._start = time()
self._show = False
self._show = not delay
self._units = units
self._print_newline = print_newline
# Only show the active jobs section if we run more than one in parallel.
self._show_jobs = False
self._active = 0
def update(self, inc=1):
# When quiet, never show any output. It's a bit hacky, but reusing the
# existing logic that delays initial output keeps the rest of the class
# clean. Basically we set the start time to years in the future.
if quiet:
self._show = False
self._start += 2**32
def start(self, name):
self._active += 1
if not self._show_jobs:
self._show_jobs = self._active > 1
self.update(inc=0, msg='started ' + name)
def finish(self, name):
self.update(msg='finished ' + name)
self._active -= 1
def update(self, inc=1, msg=''):
self._done += inc
if _NOT_TTY or IsTrace():
@ -43,36 +90,47 @@ class Progress(object):
return
if self._total <= 0:
sys.stderr.write('\r%s: %d, ' % (
self._title,
self._done))
sys.stderr.write('\r%s: %d,%s' % (
self._title,
self._done,
CSI_ERASE_LINE_AFTER))
sys.stderr.flush()
else:
p = (100 * self._done) / self._total
if self._lastp != p:
self._lastp = p
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s) ' % (
if self._show_jobs:
jobs = '[%d job%s] ' % (self._active, 's' if self._active > 1 else '')
else:
jobs = ''
sys.stderr.write('\r%s: %2d%% %s(%d%s/%d%s)%s%s%s%s' % (
self._title,
p,
jobs,
self._done, self._units,
self._total, self._units))
sys.stderr.flush()
self._total, self._units,
' ' if msg else '', msg,
CSI_ERASE_LINE_AFTER,
'\n' if self._print_newline else ''))
sys.stderr.flush()
def end(self):
if _NOT_TTY or IsTrace() or not self._show:
return
duration = duration_str(time() - self._start)
if self._total <= 0:
sys.stderr.write('\r%s: %d, done. \n' % (
self._title,
self._done))
sys.stderr.write('\r%s: %d, done in %s%s\n' % (
self._title,
self._done,
duration,
CSI_ERASE_LINE_AFTER))
sys.stderr.flush()
else:
p = (100 * self._done) / self._total
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done. \n' % (
self._title,
p,
self._done, self._units,
self._total, self._units))
sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done in %s%s\n' % (
self._title,
p,
self._done, self._units,
self._total, self._units,
duration,
CSI_ERASE_LINE_AFTER))
sys.stderr.flush()

3215
project.py

File diff suppressed because it is too large Load Diff

2
release/README.md Normal file
View File

@ -0,0 +1,2 @@
These are helper tools for managing official releases.
See the [release process](../docs/release-process.md) document for more details.

135
release/sign-launcher.py Executable file
View File

@ -0,0 +1,135 @@
#!/usr/bin/env python3
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper tool for signing repo launcher scripts correctly.
This is intended to be run only by the official Repo release managers.
"""
import argparse
import os
import re
import subprocess
import sys
import util
def sign(opts):
"""Sign the launcher!"""
output = ''
for key in opts.keys:
# We use ! at the end of the key so that gpg uses this specific key.
# Otherwise it uses the key as a lookup into the overall key and uses the
# default signing key. i.e. It will see that KEYID_RSA is a subkey of
# another key, and use the primary key to sign instead of the subkey.
cmd = ['gpg', '--homedir', opts.gpgdir, '-u', f'{key}!', '--batch', '--yes',
'--armor', '--detach-sign', '--output', '-', opts.launcher]
ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE)
output += ret.stdout
# Save the combined signatures into one file.
with open(f'{opts.launcher}.asc', 'w', encoding='utf-8') as fp:
fp.write(output)
def check(opts):
"""Check the signature."""
util.run(opts, ['gpg', '--verify', f'{opts.launcher}.asc'])
def get_version(opts):
"""Get the version from |launcher|."""
# Make sure we don't search $PATH when signing the "repo" file in the cwd.
launcher = os.path.join('.', opts.launcher)
cmd = [launcher, '--version']
ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE)
m = re.search(r'repo launcher version ([0-9.]+)', ret.stdout)
if not m:
sys.exit(f'{opts.launcher}: unable to detect repo version')
return m.group(1)
def postmsg(opts, version):
"""Helpful info to show at the end for release manager."""
print(f"""
Repo launcher bucket:
gs://git-repo-downloads/
You should first upload it with a specific version:
gsutil cp -a public-read {opts.launcher} gs://git-repo-downloads/repo-{version}
gsutil cp -a public-read {opts.launcher}.asc gs://git-repo-downloads/repo-{version}.asc
Then to make it the public default:
gsutil cp -a public-read gs://git-repo-downloads/repo-{version} gs://git-repo-downloads/repo
gsutil cp -a public-read gs://git-repo-downloads/repo-{version}.asc gs://git-repo-downloads/repo.asc
NB: If a rollback is necessary, the GS bucket archives old versions, and may be
accessed by specifying their unique id number.
gsutil ls -la gs://git-repo-downloads/repo gs://git-repo-downloads/repo.asc
gsutil cp -a public-read gs://git-repo-downloads/repo#<unique id> gs://git-repo-downloads/repo
gsutil cp -a public-read gs://git-repo-downloads/repo.asc#<unique id> gs://git-repo-downloads/repo.asc
""")
def get_parser():
"""Get a CLI parser."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-n', '--dry-run',
dest='dryrun', action='store_true',
help='show everything that would be done')
parser.add_argument('--gpgdir',
default=os.path.join(util.HOMEDIR, '.gnupg', 'repo'),
help='path to dedicated gpg dir with release keys '
'(default: ~/.gnupg/repo/)')
parser.add_argument('--keyid', dest='keys', default=[], action='append',
help='alternative signing keys to use')
parser.add_argument('launcher',
default=os.path.join(util.TOPDIR, 'repo'), nargs='?',
help='the launcher script to sign')
return parser
def main(argv):
"""The main func!"""
parser = get_parser()
opts = parser.parse_args(argv)
if not os.path.exists(opts.gpgdir):
parser.error(f'--gpgdir does not exist: {opts.gpgdir}')
if not os.path.exists(opts.launcher):
parser.error(f'launcher does not exist: {opts.launcher}')
opts.launcher = os.path.relpath(opts.launcher)
print(f'Signing "{opts.launcher}" launcher script and saving to '
f'"{opts.launcher}.asc"')
if opts.keys:
print(f'Using custom keys to sign: {" ".join(opts.keys)}')
else:
print('Using official Repo release keys to sign')
opts.keys = [util.KEYID_DSA, util.KEYID_RSA, util.KEYID_ECC]
util.import_release_key(opts)
version = get_version(opts)
sign(opts)
check(opts)
postmsg(opts, version)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

140
release/sign-tag.py Executable file
View File

@ -0,0 +1,140 @@
#!/usr/bin/env python3
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper tool for signing repo release tags correctly.
This is intended to be run only by the official Repo release managers, but it
could be run by people maintaining their own fork of the project.
NB: Check docs/release-process.md for production freeze information.
"""
import argparse
import os
import re
import subprocess
import sys
import util
# We currently sign with the old DSA key as it's been around the longest.
# We should transition to RSA by Jun 2020, and ECC by Jun 2021.
KEYID = util.KEYID_DSA
# Regular expression to validate tag names.
RE_VALID_TAG = r'^v([0-9]+[.])+[0-9]+$'
def sign(opts):
"""Tag the commit & sign it!"""
# We use ! at the end of the key so that gpg uses this specific key.
# Otherwise it uses the key as a lookup into the overall key and uses the
# default signing key. i.e. It will see that KEYID_RSA is a subkey of
# another key, and use the primary key to sign instead of the subkey.
cmd = ['git', 'tag', '-s', opts.tag, '-u', f'{opts.key}!',
'-m', f'repo {opts.tag}', opts.commit]
key = 'GNUPGHOME'
print('+', f'export {key}="{opts.gpgdir}"')
oldvalue = os.getenv(key)
os.putenv(key, opts.gpgdir)
util.run(opts, cmd)
if oldvalue is None:
os.unsetenv(key)
else:
os.putenv(key, oldvalue)
def check(opts):
"""Check the signature."""
util.run(opts, ['git', 'tag', '--verify', opts.tag])
def postmsg(opts):
"""Helpful info to show at the end for release manager."""
cmd = ['git', 'rev-parse', 'remotes/origin/stable']
ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE)
current_release = ret.stdout.strip()
cmd = ['git', 'log', '--format=%h (%aN) %s', '--no-merges',
f'remotes/origin/stable..{opts.tag}']
ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE)
shortlog = ret.stdout.strip()
print(f"""
Here's the short log since the last release.
{shortlog}
To push release to the public:
git push origin {opts.commit}:stable {opts.tag} -n
NB: People will start upgrading to this version immediately.
To roll back a release:
git push origin --force {current_release}:stable -n
""")
def get_parser():
"""Get a CLI parser."""
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-n', '--dry-run',
dest='dryrun', action='store_true',
help='show everything that would be done')
parser.add_argument('--gpgdir',
default=os.path.join(util.HOMEDIR, '.gnupg', 'repo'),
help='path to dedicated gpg dir with release keys '
'(default: ~/.gnupg/repo/)')
parser.add_argument('-f', '--force', action='store_true',
help='force signing of any tag')
parser.add_argument('--keyid', dest='key',
help='alternative signing key to use')
parser.add_argument('tag',
help='the tag to create (e.g. "v2.0")')
parser.add_argument('commit', default='HEAD', nargs='?',
help='the commit to tag')
return parser
def main(argv):
"""The main func!"""
parser = get_parser()
opts = parser.parse_args(argv)
if not os.path.exists(opts.gpgdir):
parser.error(f'--gpgdir does not exist: {opts.gpgdir}')
if not opts.force and not re.match(RE_VALID_TAG, opts.tag):
parser.error(f'tag "{opts.tag}" does not match regex "{RE_VALID_TAG}"; '
'use --force to sign anyways')
if opts.key:
print(f'Using custom key to sign: {opts.key}')
else:
print('Using official Repo release key to sign')
opts.key = KEYID
util.import_release_key(opts)
sign(opts)
check(opts)
postmsg(opts)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

110
release/update-manpages Executable file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env python3
# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper tool for generating manual page for all repo commands.
This is intended to be run before every official Repo release.
"""
from pathlib import Path
from functools import partial
import argparse
import multiprocessing
import os
import re
import shutil
import subprocess
import sys
import tempfile
TOPDIR = Path(__file__).resolve().parent.parent
MANDIR = TOPDIR.joinpath('man')
# Load repo local modules.
sys.path.insert(0, str(TOPDIR))
from git_command import RepoSourceVersion
import subcmds
def worker(cmd, **kwargs):
subprocess.run(cmd, **kwargs)
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
opts = parser.parse_args(argv)
if not shutil.which('help2man'):
sys.exit('Please install help2man to continue.')
# Let repo know we're generating man pages so it can avoid some dynamic
# behavior (like probing active number of CPUs). We use a weird name &
# value to make it less likely for users to set this var themselves.
os.environ['_REPO_GENERATE_MANPAGES_'] = ' indeed! '
# "repo branch" is an alias for "repo branches".
del subcmds.all_commands['branch']
(MANDIR / 'repo-branch.1').write_text('.so man1/repo-branches.1')
version = RepoSourceVersion()
cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}',
'-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}',
'-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), './repo',
'-h', f'help {cmd}'] for cmd in subcmds.all_commands]
cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git',
'-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}',
'-o', MANDIR.joinpath('repo.1.tmp'), './repo',
'-h', '--help-all'])
with tempfile.TemporaryDirectory() as tempdir:
tempdir = Path(tempdir)
repo_dir = tempdir / '.repo'
repo_dir.mkdir()
(repo_dir / 'repo').symlink_to(TOPDIR)
# Create a repo wrapper using the active Python executable. We can't pass
# this directly to help2man as it's too simple, so insert it via shebang.
data = (TOPDIR / 'repo').read_text(encoding='utf-8')
tempbin = tempdir / 'repo'
tempbin.write_text(f'#!{sys.executable}\n' + data, encoding='utf-8')
tempbin.chmod(0o755)
# Run all cmd in parallel, and wait for them to finish.
with multiprocessing.Pool() as pool:
pool.map(partial(worker, cwd=tempdir, check=True), cmdlist)
regex = (
(r'(It was generated by help2man) [0-9.]+', '\g<1>.'),
(r'^\.IP\n(.*:)\n', '.SS \g<1>\n'),
(r'^\.PP\nDescription', '.SH DETAILS'),
)
for tmp_path in MANDIR.glob('*.1.tmp'):
path = tmp_path.parent / tmp_path.stem
old_data = path.read_text() if path.exists() else ''
data = tmp_path.read_text()
tmp_path.unlink()
for pattern, replacement in regex:
data = re.sub(pattern, replacement, data, flags=re.M)
# If the only thing that changed was the date, don't refresh. This avoids
# a lot of noise when only one file actually updates.
old_data = re.sub(r'^(\.TH REPO "1" ")([^"]+)', r'\1', old_data, flags=re.M)
new_data = re.sub(r'^(\.TH REPO "1" ")([^"]+)', r'\1', data, flags=re.M)
if old_data != new_data:
path.write_text(data)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

73
release/util.py Normal file
View File

@ -0,0 +1,73 @@
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Random utility code for release tools."""
import os
import re
import subprocess
import sys
assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
TOPDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
HOMEDIR = os.path.expanduser('~')
# These are the release keys we sign with.
KEYID_DSA = '8BB9AD793E8E6153AF0F9A4416530D5E920F5C65'
KEYID_RSA = 'A34A13BE8E76BFF46A0C022DA2E75A824AAB9624'
KEYID_ECC = 'E1F9040D7A3F6DAFAC897CD3D3B95DA243E48A39'
def cmdstr(cmd):
"""Get a nicely quoted shell command."""
ret = []
for arg in cmd:
if not re.match(r'^[a-zA-Z0-9/_.=-]+$', arg):
arg = f'"{arg}"'
ret.append(arg)
return ' '.join(ret)
def run(opts, cmd, check=True, **kwargs):
"""Helper around subprocess.run to include logging."""
print('+', cmdstr(cmd))
if opts.dryrun:
cmd = ['true', '--'] + cmd
try:
return subprocess.run(cmd, check=check, **kwargs)
except subprocess.CalledProcessError as e:
print(f'aborting: {e}', file=sys.stderr)
sys.exit(1)
def import_release_key(opts):
"""Import the public key of the official release repo signing key."""
# Extract the key from our repo launcher.
launcher = getattr(opts, 'launcher', os.path.join(TOPDIR, 'repo'))
print(f'Importing keys from "{launcher}" launcher script')
with open(launcher, encoding='utf-8') as fp:
data = fp.read()
keys = re.findall(
r'\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n[^-]*'
r'\n-----END PGP PUBLIC KEY BLOCK-----\n', data, flags=re.M)
run(opts, ['gpg', '--import'], input='\n'.join(keys).encode('utf-8'))
print('Marking keys as fully trusted')
run(opts, ['gpg', '--import-ownertrust'],
input=f'{KEYID_DSA}:6:\n'.encode('utf-8'))

1331
repo

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,23 +12,29 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
"""Logic for tracing repo interactions.
Activated via `repo --trace ...` or `REPO_TRACE=1 repo ...`.
"""
import sys
import os
# Env var to implicitly turn on tracing.
REPO_TRACE = 'REPO_TRACE'
try:
_TRACE = os.environ[REPO_TRACE] == '1'
except KeyError:
_TRACE = False
_TRACE = os.environ.get(REPO_TRACE) == '1'
def IsTrace():
return _TRACE
def SetTrace():
global _TRACE
_TRACE = True
def Trace(fmt, *args):
if IsTrace():
print(fmt % args, file=sys.stderr)

57
requirements.json Normal file
View File

@ -0,0 +1,57 @@
# This file declares various requirements for this version of repo. The
# launcher script will load it and check the constraints before trying to run
# us. This avoids issues of the launcher using an old version of Python (e.g.
# 3.5) while the codebase has moved on to requiring something much newer (e.g.
# 3.8). If the launcher tried to import us, it would fail with syntax errors.
# This is a JSON file with line-level comments allowed.
# Always keep backwards compatibility in mine. The launcher script is robust
# against missing values, but when a field is renamed/removed, it means older
# versions of the launcher script won't be able to enforce the constraint.
# When requiring versions, always use lists as they are easy to parse & compare
# in Python. Strings would require futher processing to turn into a list.
# Version constraints should be expressed in pairs: soft & hard. Soft versions
# are when we start warning users that their software too old and we're planning
# on dropping support for it, so they need to start planning system upgrades.
# Hard versions are when we refuse to work the tool. Users will be shown an
# error message before we abort entirely.
# When deciding whether to upgrade a version requirement, check out the distro
# lists to see who will be impacted:
# https://gerrit.googlesource.com/git-repo/+/HEAD/docs/release-process.md#Project-References
{
# The repo launcher itself. This allows us to force people to upgrade as some
# ignore the warnings about it being out of date, or install ancient versions
# to start with for whatever reason.
#
# NB: Repo launchers started checking this file with repo-2.12, so listing
# versions older than that won't make a difference.
"repo": {
"hard": [2, 11],
"soft": [2, 11]
},
# Supported Python versions.
#
# python-3.6 is in Ubuntu Bionic.
# python-3.7 is in Debian Buster.
"python": {
"hard": [3, 6],
"soft": [3, 6]
},
# Supported git versions.
#
# git-1.7.2 is in Debian Squeeze.
# git-1.7.9 is in Ubuntu Precise.
# git-1.9.1 is in Ubuntu Trusty.
# git-1.7.10 is in Debian Wheezy.
"git": {
"hard": [1, 7, 2],
"soft": [1, 9, 1]
}
}

61
run_tests Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
# Copyright 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Wrapper to run pytest with the right settings."""
import errno
import os
import shutil
import subprocess
import sys
def find_pytest():
"""Try to locate a good version of pytest."""
# If we're in a virtualenv, assume that it's provided the right pytest.
if 'VIRTUAL_ENV' in os.environ:
return 'pytest'
# Use the Python 3 version if available.
ret = shutil.which('pytest-3')
if ret:
return ret
# Hopefully this is a Python 3 version.
ret = shutil.which('pytest')
if ret:
return ret
print('%s: unable to find pytest.' % (__file__,), file=sys.stderr)
print('%s: Try installing: sudo apt-get install python-pytest' % (__file__,),
file=sys.stderr)
def main(argv):
"""The main entry."""
# Add the repo tree to PYTHONPATH as the tests expect to be able to import
# modules directly.
pythonpath = os.path.dirname(os.path.realpath(__file__))
oldpythonpath = os.environ.get('PYTHONPATH', None)
if oldpythonpath is not None:
pythonpath += os.pathsep + oldpythonpath
os.environ['PYTHONPATH'] = pythonpath
pytest = find_pytest()
return subprocess.run([pytest] + argv, check=False).returncode
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

61
setup.py Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
# Copyright 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the 'License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Python packaging for repo."""
import os
import setuptools
TOPDIR = os.path.dirname(os.path.abspath(__file__))
# Rip out the first intro paragraph.
with open(os.path.join(TOPDIR, 'README.md')) as fp:
lines = fp.read().splitlines()[2:]
end = lines.index('')
long_description = ' '.join(lines[0:end])
# https://packaging.python.org/tutorials/packaging-projects/
setuptools.setup(
name='repo',
version='2',
maintainer='Various',
maintainer_email='repo-discuss@googlegroups.com',
description='Repo helps manage many Git repositories',
long_description=long_description,
long_description_content_type='text/plain',
url='https://gerrit.googlesource.com/git-repo/',
project_urls={
'Bug Tracker': 'https://bugs.chromium.org/p/gerrit/issues/list?q=component:repo',
},
# https://pypi.org/classifiers/
classifiers=[
'Development Status :: 6 - Mature',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Natural Language :: English',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows :: Windows 10',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development :: Version Control :: Git',
],
python_requires='>=3.6',
packages=['subcmds'],
)

280
ssh.py Normal file
View File

@ -0,0 +1,280 @@
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common SSH management logic."""
import functools
import multiprocessing
import os
import re
import signal
import subprocess
import sys
import tempfile
import time
import platform_utils
from repo_trace import Trace
PROXY_PATH = os.path.join(os.path.dirname(__file__), 'git_ssh')
def _run_ssh_version():
"""run ssh -V to display the version number"""
return subprocess.check_output(['ssh', '-V'], stderr=subprocess.STDOUT).decode()
def _parse_ssh_version(ver_str=None):
"""parse a ssh version string into a tuple"""
if ver_str is None:
ver_str = _run_ssh_version()
m = re.match(r'^OpenSSH_([0-9.]+)(p[0-9]+)?\s', ver_str)
if m:
return tuple(int(x) for x in m.group(1).split('.'))
else:
return ()
@functools.lru_cache(maxsize=None)
def version():
"""return ssh version as a tuple"""
try:
return _parse_ssh_version()
except FileNotFoundError:
print('fatal: ssh not installed', file=sys.stderr)
sys.exit(1)
except subprocess.CalledProcessError:
print('fatal: unable to detect ssh version', file=sys.stderr)
sys.exit(1)
URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
class ProxyManager:
"""Manage various ssh clients & masters that we spawn.
This will take care of sharing state between multiprocessing children, and
make sure that if we crash, we don't leak any of the ssh sessions.
The code should work with a single-process scenario too, and not add too much
overhead due to the manager.
"""
# Path to the ssh program to run which will pass our master settings along.
# Set here more as a convenience API.
proxy = PROXY_PATH
def __init__(self, manager):
# Protect access to the list of active masters.
self._lock = multiprocessing.Lock()
# List of active masters (pid). These will be spawned on demand, and we are
# responsible for shutting them all down at the end.
self._masters = manager.list()
# Set of active masters indexed by "host:port" information.
# The value isn't used, but multiprocessing doesn't provide a set class.
self._master_keys = manager.dict()
# Whether ssh masters are known to be broken, so we give up entirely.
self._master_broken = manager.Value('b', False)
# List of active ssh sesssions. Clients will be added & removed as
# connections finish, so this list is just for safety & cleanup if we crash.
self._clients = manager.list()
# Path to directory for holding master sockets.
self._sock_path = None
def __enter__(self):
"""Enter a new context."""
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Exit a context & clean up all resources."""
self.close()
def add_client(self, proc):
"""Track a new ssh session."""
self._clients.append(proc.pid)
def remove_client(self, proc):
"""Remove a completed ssh session."""
try:
self._clients.remove(proc.pid)
except ValueError:
pass
def add_master(self, proc):
"""Track a new master connection."""
self._masters.append(proc.pid)
def _terminate(self, procs):
"""Kill all |procs|."""
for pid in procs:
try:
os.kill(pid, signal.SIGTERM)
os.waitpid(pid, 0)
except OSError:
pass
# The multiprocessing.list() API doesn't provide many standard list()
# methods, so we have to manually clear the list.
while True:
try:
procs.pop(0)
except:
break
def close(self):
"""Close this active ssh session.
Kill all ssh clients & masters we created, and nuke the socket dir.
"""
self._terminate(self._clients)
self._terminate(self._masters)
d = self.sock(create=False)
if d:
try:
platform_utils.rmdir(os.path.dirname(d))
except OSError:
pass
def _open_unlocked(self, host, port=None):
"""Make sure a ssh master session exists for |host| & |port|.
If one doesn't exist already, we'll create it.
We won't grab any locks, so the caller has to do that. This helps keep the
business logic of actually creating the master separate from grabbing locks.
"""
# Check to see whether we already think that the master is running; if we
# think it's already running, return right away.
if port is not None:
key = '%s:%s' % (host, port)
else:
key = host
if key in self._master_keys:
return True
if self._master_broken.value or 'GIT_SSH' in os.environ:
# Failed earlier, so don't retry.
return False
# We will make two calls to ssh; this is the common part of both calls.
command_base = ['ssh', '-o', 'ControlPath %s' % self.sock(), host]
if port is not None:
command_base[1:1] = ['-p', str(port)]
# Since the key wasn't in _master_keys, we think that master isn't running.
# ...but before actually starting a master, we'll double-check. This can
# be important because we can't tell that that 'git@myhost.com' is the same
# as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
check_command = command_base + ['-O', 'check']
try:
Trace(': %s', ' '.join(check_command))
check_process = subprocess.Popen(check_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
check_process.communicate() # read output, but ignore it...
isnt_running = check_process.wait()
if not isnt_running:
# Our double-check found that the master _was_ infact running. Add to
# the list of keys.
self._master_keys[key] = True
return True
except Exception:
# Ignore excpetions. We we will fall back to the normal command and print
# to the log there.
pass
command = command_base[:1] + ['-M', '-N'] + command_base[1:]
try:
Trace(': %s', ' '.join(command))
p = subprocess.Popen(command)
except Exception as e:
self._master_broken.value = True
print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
% (host, port, str(e)), file=sys.stderr)
return False
time.sleep(1)
ssh_died = (p.poll() is not None)
if ssh_died:
return False
self.add_master(p)
self._master_keys[key] = True
return True
def _open(self, host, port=None):
"""Make sure a ssh master session exists for |host| & |port|.
If one doesn't exist already, we'll create it.
This will obtain any necessary locks to avoid inter-process races.
"""
# Bail before grabbing the lock if we already know that we aren't going to
# try creating new masters below.
if sys.platform in ('win32', 'cygwin'):
return False
# Acquire the lock. This is needed to prevent opening multiple masters for
# the same host when we're running "repo sync -jN" (for N > 1) _and_ the
# manifest <remote fetch="ssh://xyz"> specifies a different host from the
# one that was passed to repo init.
with self._lock:
return self._open_unlocked(host, port)
def preconnect(self, url):
"""If |uri| will create a ssh connection, setup the ssh master for it."""
m = URI_ALL.match(url)
if m:
scheme = m.group(1)
host = m.group(2)
if ':' in host:
host, port = host.split(':')
else:
port = None
if scheme in ('ssh', 'git+ssh', 'ssh+git'):
return self._open(host, port)
return False
m = URI_SCP.match(url)
if m:
host = m.group(1)
return self._open(host)
return False
def sock(self, create=True):
"""Return the path to the ssh socket dir.
This has all the master sockets so clients can talk to them.
"""
if self._sock_path is None:
if not create:
return None
tmp_dir = '/tmp'
if not os.path.exists(tmp_dir):
tmp_dir = tempfile.gettempdir()
if version() < (6, 7):
tokens = '%r@%h:%p'
else:
tokens = '%C' # hash of %l%h%p%r
self._sock_path = os.path.join(
tempfile.mkdtemp('', 'ssh-', tmp_dir),
'master-' + tokens)
return self._sock_path

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -15,6 +14,7 @@
import os
# A mapping of the subcommand name to the class that implements it.
all_commands = {}
my_dir = os.path.dirname(__file__)
@ -36,14 +36,14 @@ for py in os.listdir(my_dir):
['%s' % name])
mod = getattr(mod, name)
try:
cmd = getattr(mod, clsn)()
cmd = getattr(mod, clsn)
except AttributeError:
raise SyntaxError('%s/%s does not define class %s' % (
__name__, py, clsn))
__name__, py, clsn))
name = name.replace('_', '-')
cmd.NAME = name
all_commands[name] = cmd
if 'help' in all_commands:
all_commands['help'].commands = all_commands
# Add 'branch' as an alias for 'branches'.
all_commands['branch'] = all_commands['branches']

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,59 +12,104 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from collections import defaultdict
import functools
import itertools
import sys
from command import Command
from command import Command, DEFAULT_LOCAL_JOBS
from git_command import git
from progress import Progress
class Abandon(Command):
common = True
COMMON = True
helpSummary = "Permanently abandon a development branch"
helpUsage = """
%prog <branchname> [<project>...]
%prog [--all | <branchname>] [<project>...]
This subcommand permanently abandons a development branch by
deleting it (and all its history) from your local repository.
It is equivalent to "git branch -D <branchname>".
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def Execute(self, opt, args):
if not args:
def _Options(self, p):
p.add_option('--all',
dest='all', action='store_true',
help='delete all branches in all projects')
def ValidateOptions(self, opt, args):
if not opt.all and not args:
self.Usage()
nb = args[0]
if not git.check_ref_format('heads/%s' % nb):
print("error: '%s' is not a valid name" % nb, file=sys.stderr)
sys.exit(1)
if not opt.all:
nb = args[0]
if not git.check_ref_format('heads/%s' % nb):
self.OptionParser.error("'%s' is not a valid branch name" % nb)
else:
args.insert(0, "'All local branches'")
nb = args[0]
err = []
success = []
all_projects = self.GetProjects(args[1:])
def _ExecuteOne(self, all_branches, nb, project):
"""Abandon one project."""
if all_branches:
branches = project.GetBranches()
else:
branches = [nb]
pm = Progress('Abandon %s' % nb, len(all_projects))
for project in all_projects:
pm.update()
status = project.AbandonBranch(nb)
ret = {}
for name in branches:
status = project.AbandonBranch(name)
if status is not None:
if status:
success.append(project)
else:
err.append(project)
pm.end()
ret[name] = status
return (ret, project)
def Execute(self, opt, args):
nb = args[0]
err = defaultdict(list)
success = defaultdict(list)
all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only)
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
def _ProcessResults(_pool, pm, states):
for (results, project) in states:
for branch, status in results.items():
if status:
success[branch].append(project)
else:
err[branch].append(project)
pm.update()
self.ExecuteInParallel(
opt.jobs,
functools.partial(self._ExecuteOne, opt.all, nb),
all_projects,
callback=_ProcessResults,
output=Progress('Abandon %s' % (nb,), len(all_projects), quiet=opt.quiet))
width = max(itertools.chain(
[25], (len(x) for x in itertools.chain(success, err))))
if err:
for p in err:
print("error: %s/: cannot abandon %s" % (p.relpath, nb),
file=sys.stderr)
for br in err.keys():
err_msg = "error: cannot abandon %s" % br
print(err_msg, file=sys.stderr)
for proj in err[br]:
print(' ' * len(err_msg) + " | %s" % _RelPath(proj), file=sys.stderr)
sys.exit(1)
elif not success:
print('error: no project has branch %s' % nb, file=sys.stderr)
print('error: no project has local branch(es) : %s' % nb,
file=sys.stderr)
sys.exit(1)
else:
print('Abandoned in %d project(s):\n %s'
% (len(success), '\n '.join(p.relpath for p in success)),
file=sys.stderr)
# Everything below here is displaying status.
if opt.quiet:
return
print('Abandoned branches:')
for br in success.keys():
if len(all_projects) > 1 and len(all_projects) == len(success[br]):
result = "all project"
else:
result = "%s" % (
('\n' + ' ' * width + '| ').join(_RelPath(p) for p in success[br]))
print("%s%s| %s\n" % (br, ' ' * (width - len(br)), result))

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,18 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import itertools
import sys
from color import Coloring
from command import Command
from command import Command, DEFAULT_LOCAL_JOBS
class BranchColoring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'branch')
self.current = self.printer('current', fg='green')
self.local = self.printer('local')
self.local = self.printer('local')
self.notinproject = self.printer('notinproject', fg='red')
class BranchInfo(object):
def __init__(self, name):
self.name = name
@ -46,6 +48,10 @@ class BranchInfo(object):
def IsCurrent(self):
return self.current > 0
@property
def IsSplitCurrent(self):
return self.current != 0 and self.current != len(self.projects)
@property
def IsPublished(self):
return self.published > 0
@ -56,15 +62,14 @@ class BranchInfo(object):
class Branches(Command):
common = True
COMMON = True
helpSummary = "View current topic branches"
helpUsage = """
%prog [<project>...]
Summarizes the currently available topic branches.
Branch Display
--------------
# Branch Display
The branch display output by this command is organized into four
columns of information; for example:
@ -90,21 +95,27 @@ the branch appears in, or does not appear in. If no project list
is shown, then the branch appears in all projects.
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def Execute(self, opt, args):
projects = self.GetProjects(args)
projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
out = BranchColoring(self.manifest.manifestProject.config)
all_branches = {}
project_cnt = len(projects)
for project in projects:
for name, b in project.GetBranches().items():
b.project = project
def _ProcessResults(_pool, _output, results):
for name, b in itertools.chain.from_iterable(results):
if name not in all_branches:
all_branches[name] = BranchInfo(name)
all_branches[name].add(b)
names = list(sorted(all_branches))
self.ExecuteInParallel(
opt.jobs,
expand_project_to_branches,
projects,
callback=_ProcessResults)
names = sorted(all_branches)
if not names:
print(' (no branches)', file=sys.stderr)
@ -136,31 +147,59 @@ is shown, then the branch appears in all projects.
hdr('%c%c %-*s' % (current, published, width, name))
out.write(' |')
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
if in_cnt < project_cnt:
fmt = out.write
paths = []
if in_cnt < project_cnt - in_cnt:
non_cur_paths = []
if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt):
in_type = 'in'
for b in i.projects:
paths.append(b.project.relpath)
relpath = b.project.relpath
if not i.IsSplitCurrent or b.current:
paths.append(_RelPath(b.project))
else:
non_cur_paths.append(_RelPath(b.project))
else:
fmt = out.notinproject
in_type = 'not in'
have = set()
for b in i.projects:
have.add(b.project)
have.add(_RelPath(b.project))
for p in projects:
if not p in have:
paths.append(p.relpath)
if _RelPath(p) not in have:
paths.append(_RelPath(p))
s = ' %s %s' % (in_type, ', '.join(paths))
if width + 7 + len(s) < 80:
if not i.IsSplitCurrent and (width + 7 + len(s) < 80):
fmt = out.current if i.IsCurrent else fmt
fmt(s)
else:
fmt(' %s:' % in_type)
fmt = out.current if i.IsCurrent else out.write
for p in paths:
out.nl()
fmt(width*' ' + ' %s' % p)
fmt(width * ' ' + ' %s' % p)
fmt = out.write
for p in non_cur_paths:
out.nl()
fmt(width * ' ' + ' %s' % p)
else:
out.write(' in all projects')
out.nl()
def expand_project_to_branches(project):
"""Expands a project into a list of branch names & associated information.
Args:
project: project.Project
Returns:
List[Tuple[str, git_config.Branch]]
"""
branches = []
for name, b in project.GetBranches().items():
b.project = project
branches.append((name, b))
return branches

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,13 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import functools
import sys
from command import Command
from command import Command, DEFAULT_LOCAL_JOBS
from progress import Progress
class Checkout(Command):
common = True
COMMON = True
helpSummary = "Checkout a branch for development"
helpUsage = """
%prog <branchname> [<project>...]
@ -32,27 +33,37 @@ The command is equivalent to:
repo forall [<project>...] -c git checkout <branchname>
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def Execute(self, opt, args):
def ValidateOptions(self, opt, args):
if not args:
self.Usage()
def _ExecuteOne(self, nb, project):
"""Checkout one project."""
return (project.CheckoutBranch(nb), project)
def Execute(self, opt, args):
nb = args[0]
err = []
success = []
all_projects = self.GetProjects(args[1:])
all_projects = self.GetProjects(args[1:], all_manifests=not opt.this_manifest_only)
pm = Progress('Checkout %s' % nb, len(all_projects))
for project in all_projects:
pm.update()
def _ProcessResults(_pool, pm, results):
for status, project in results:
if status is not None:
if status:
success.append(project)
else:
err.append(project)
pm.update()
status = project.CheckoutBranch(nb)
if status is not None:
if status:
success.append(project)
else:
err.append(project)
pm.end()
self.ExecuteInParallel(
opt.jobs,
functools.partial(self._ExecuteOne, nb),
all_projects,
callback=_ProcessResults,
output=Progress('Checkout %s' % (nb,), len(all_projects), quiet=opt.quiet))
if err:
for p in err:

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2010 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import re
import sys
from command import Command
@ -21,8 +19,9 @@ from git_command import GitCommand
CHANGE_ID_RE = re.compile(r'^\s*Change-Id: I([0-9a-f]{40})\s*$')
class CherryPick(Command):
common = True
COMMON = True
helpSummary = "Cherry-pick a change."
helpUsage = """
%prog <sha1>
@ -33,19 +32,17 @@ The change id will be updated, and a reference to the old
change id will be added.
"""
def _Options(self, p):
pass
def Execute(self, opt, args):
def ValidateOptions(self, opt, args):
if len(args) != 1:
self.Usage()
def Execute(self, opt, args):
reference = args[0]
p = GitCommand(None,
['rev-parse', '--verify', reference],
capture_stdout = True,
capture_stderr = True)
capture_stdout=True,
capture_stderr=True)
if p.Wait() != 0:
print(p.stderr, file=sys.stderr)
sys.exit(1)
@ -59,12 +56,14 @@ change id will be added.
p = GitCommand(None,
['cherry-pick', sha1],
capture_stdout = True,
capture_stderr = True)
capture_stdout=True,
capture_stderr=True)
status = p.Wait()
print(p.stdout, file=sys.stdout)
print(p.stderr, file=sys.stderr)
if p.stdout:
print(p.stdout.strip(), file=sys.stdout)
if p.stderr:
print(p.stderr.strip(), file=sys.stderr)
if status == 0:
# The cherry-pick was applied correctly. We just need to edit the
@ -72,10 +71,9 @@ change id will be added.
new_msg = self._Reformat(old_msg, sha1)
p = GitCommand(None, ['commit', '--amend', '-F', '-'],
provide_stdin = True,
capture_stdout = True,
capture_stderr = True)
p.stdin.write(new_msg)
input=new_msg,
capture_stdout=True,
capture_stderr=True)
if p.Wait() != 0:
print("error: Failed to update commit message", file=sys.stderr)
sys.exit(1)
@ -94,7 +92,7 @@ change id will be added.
def _StripHeader(self, commit_msg):
lines = commit_msg.splitlines()
return "\n".join(lines[lines.index("")+1:])
return "\n".join(lines[lines.index("") + 1:])
def _Reformat(self, old_msg, sha1):
new_msg = []

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,10 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from command import PagedCommand
import functools
import io
from command import DEFAULT_LOCAL_JOBS, PagedCommand
class Diff(PagedCommand):
common = True
COMMON = True
helpSummary = "Show changes between commit and working tree"
helpUsage = """
%prog [<project>...]
@ -25,16 +28,45 @@ The -u option causes '%prog' to generate diff output with file paths
relative to the repository root, so the output can be applied
to the Unix 'patch' command.
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def _Options(self, p):
def cmd(option, opt_str, value, parser):
setattr(parser.values, option.dest, list(parser.rargs))
while parser.rargs:
del parser.rargs[0]
p.add_option('-u', '--absolute',
dest='absolute', action='store_true',
help='Paths are relative to the repository root')
help='paths are relative to the repository root')
def _ExecuteOne(self, absolute, local, project):
"""Obtains the diff for a specific project.
Args:
absolute: Paths are relative to the root.
local: a boolean, if True, the path is relative to the local
(sub)manifest. If false, the path is relative to the
outermost manifest.
project: Project to get status of.
Returns:
The status of the project.
"""
buf = io.StringIO()
ret = project.PrintWorkTreeDiff(absolute, output_redir=buf, local=local)
return (ret, buf.getvalue())
def Execute(self, opt, args):
for project in self.GetProjects(args):
project.PrintWorkTreeDiff(opt.absolute)
all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
def _ProcessResults(_pool, _output, results):
ret = 0
for (state, output) in results:
if output:
print(output, end='')
if not state:
ret = 1
return ret
return self.ExecuteInParallel(
opt.jobs,
functools.partial(self._ExecuteOne, opt.absolute, opt.this_manifest_only),
all_projects,
callback=_ProcessResults,
ordered=True)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -15,12 +14,14 @@
from color import Coloring
from command import PagedCommand
from manifest_xml import XmlManifest
from manifest_xml import RepoClient
class _Coloring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, "status")
class Diffmanifests(PagedCommand):
""" A command to see logs in projects represented by manifests
@ -30,7 +31,7 @@ class Diffmanifests(PagedCommand):
deeper level.
"""
common = True
COMMON = True
helpSummary = "Manifest diff utility"
helpUsage = """%prog manifest1.xml [manifest2.xml] [options]"""
@ -67,12 +68,16 @@ synced and their revisions won't be found.
def _Options(self, p):
p.add_option('--raw',
dest='raw', action='store_true',
help='Display raw diff.')
help='display raw diff')
p.add_option('--no-color',
dest='color', action='store_false', default=True,
help='does not display the diff in color.')
help='does not display the diff in color')
p.add_option('--pretty-format',
dest='pretty_format', action='store',
metavar='<FORMAT>',
help='print the log using a custom git pretty format string')
def _printRawDiff(self, diff):
def _printRawDiff(self, diff, pretty_format=None):
for project in diff['added']:
self.printText("A %s %s" % (project.relpath, project.revisionExpr))
self.out.nl()
@ -85,14 +90,14 @@ synced and their revisions won't be found.
self.printText("C %s %s %s" % (project.relpath, project.revisionExpr,
otherProject.revisionExpr))
self.out.nl()
self._printLogs(project, otherProject, raw=True, color=False)
self._printLogs(project, otherProject, raw=True, color=False, pretty_format=pretty_format)
for project, otherProject in diff['unreachable']:
self.printText("U %s %s %s" % (project.relpath, project.revisionExpr,
otherProject.revisionExpr))
self.out.nl()
def _printDiff(self, diff, color=True):
def _printDiff(self, diff, color=True, pretty_format=None):
if diff['added']:
self.out.nl()
self.printText('added projects : \n')
@ -113,6 +118,16 @@ synced and their revisions won't be found.
self.printRevision(project.revisionExpr)
self.out.nl()
if diff['missing']:
self.out.nl()
self.printText('missing projects : \n')
self.out.nl()
for project in diff['missing']:
self.printProject('\t%s' % (project.relpath))
self.printText(' at revision ')
self.printRevision(project.revisionExpr)
self.out.nl()
if diff['changed']:
self.out.nl()
self.printText('changed projects : \n')
@ -124,7 +139,8 @@ synced and their revisions won't be found.
self.printText(' to ')
self.printRevision(otherProject.revisionExpr)
self.out.nl()
self._printLogs(project, otherProject, raw=False, color=color)
self._printLogs(project, otherProject, raw=False, color=color,
pretty_format=pretty_format)
self.out.nl()
if diff['unreachable']:
@ -139,9 +155,13 @@ synced and their revisions won't be found.
self.printText(' not found')
self.out.nl()
def _printLogs(self, project, otherProject, raw=False, color=True):
logs = project.getAddedAndRemovedLogs(otherProject, oneline=True,
color=color)
def _printLogs(self, project, otherProject, raw=False, color=True,
pretty_format=None):
logs = project.getAddedAndRemovedLogs(otherProject,
oneline=(pretty_format is None),
color=color,
pretty_format=pretty_format)
if logs['removed']:
removedLogs = logs['removed'].split('\n')
for log in removedLogs:
@ -166,30 +186,34 @@ synced and their revisions won't be found.
self.printText(log)
self.out.nl()
def Execute(self, opt, args):
def ValidateOptions(self, opt, args):
if not args or len(args) > 2:
self.Usage()
self.OptionParser.error('missing manifests to diff')
if opt.this_manifest_only is False:
raise self.OptionParser.error(
'`diffmanifest` only supports the current tree')
self.out = _Coloring(self.manifest.globalConfig)
def Execute(self, opt, args):
self.out = _Coloring(self.client.globalConfig)
self.printText = self.out.nofmt_printer('text')
if opt.color:
self.printProject = self.out.nofmt_printer('project', attr = 'bold')
self.printAdded = self.out.nofmt_printer('green', fg = 'green', attr = 'bold')
self.printRemoved = self.out.nofmt_printer('red', fg = 'red', attr = 'bold')
self.printRevision = self.out.nofmt_printer('revision', fg = 'yellow')
self.printProject = self.out.nofmt_printer('project', attr='bold')
self.printAdded = self.out.nofmt_printer('green', fg='green', attr='bold')
self.printRemoved = self.out.nofmt_printer('red', fg='red', attr='bold')
self.printRevision = self.out.nofmt_printer('revision', fg='yellow')
else:
self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
manifest1 = XmlManifest(self.manifest.repodir)
manifest1.Override(args[0])
manifest1 = RepoClient(self.repodir)
manifest1.Override(args[0], load_local_manifests=False)
if len(args) == 1:
manifest2 = self.manifest
else:
manifest2 = XmlManifest(self.manifest.repodir)
manifest2.Override(args[1])
manifest2 = RepoClient(self.repodir)
manifest2.Override(args[1], load_local_manifests=False)
diff = manifest1.projectsDiff(manifest2)
if opt.raw:
self._printRawDiff(diff)
self._printRawDiff(diff, pretty_format=opt.pretty_format)
else:
self._printDiff(diff, color=opt.color)
self._printDiff(diff, color=opt.color, pretty_format=opt.pretty_format)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,30 +12,35 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import re
import sys
from command import Command
from error import GitError
from error import GitError, NoSuchProjectError
CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$')
class Download(Command):
common = True
COMMON = True
helpSummary = "Download and checkout a change"
helpUsage = """
%prog {project change[/patchset]}...
%prog {[project] change[/patchset]}...
"""
helpDescription = """
The '%prog' command downloads a change from the review system and
makes it available in your project's local working directory.
If no project is specified try to use current directory as a project.
"""
def _Options(self, p):
p.add_option('-b', '--branch',
help='create a new branch first')
p.add_option('-c', '--cherry-pick',
dest='cherrypick', action='store_true',
help="cherry-pick instead of checkout")
p.add_option('-x', '--record-origin', action='store_true',
help='pass -x when cherry-picking')
p.add_option('-r', '--revert',
dest='revert', action='store_true',
help="revert instead of checkout")
@ -44,7 +48,7 @@ makes it available in your project's local working directory.
dest='ffonly', action='store_true',
help="force fast-forward merge")
def _ParseChangeIds(self, args):
def _ParseChangeIds(self, opt, args):
if not args:
self.Usage()
@ -55,19 +59,53 @@ makes it available in your project's local working directory.
m = CHANGE_RE.match(a)
if m:
if not project:
self.Usage()
project = self.GetProjects(".")[0]
print('Defaulting to cwd project', project.name)
chg_id = int(m.group(1))
if m.group(2):
ps_id = int(m.group(2))
else:
ps_id = 1
refs = 'refs/changes/%2.2d/%d/' % (chg_id % 100, chg_id)
output = project._LsRemote(refs + '*')
if output:
regex = refs + r'(\d+)'
rcomp = re.compile(regex, re.I)
for line in output.splitlines():
match = rcomp.search(line)
if match:
ps_id = max(int(match.group(1)), ps_id)
to_get.append((project, chg_id, ps_id))
else:
project = self.GetProjects([a])[0]
projects = self.GetProjects([a], all_manifests=not opt.this_manifest_only)
if len(projects) > 1:
# If the cwd is one of the projects, assume they want that.
try:
project = self.GetProjects('.')[0]
except NoSuchProjectError:
project = None
if project not in projects:
print('error: %s matches too many projects; please re-run inside '
'the project checkout.' % (a,), file=sys.stderr)
for project in projects:
print(' %s/ @ %s' % (project.RelPath(local=opt.this_manifest_only),
project.revisionExpr), file=sys.stderr)
sys.exit(1)
else:
project = projects[0]
print('Defaulting to cwd project', project.name)
return to_get
def ValidateOptions(self, opt, args):
if opt.record_origin:
if not opt.cherrypick:
self.OptionParser.error('-x only makes sense with --cherry-pick')
if opt.ffonly:
self.OptionParser.error('-x and --ff are mutually exclusive options')
def Execute(self, opt, args):
for project, change_id, ps_id in self._ParseChangeIds(args):
for project, change_id, ps_id in self._ParseChangeIds(opt, args):
dl = project.DownloadPatchSet(change_id, ps_id)
if not dl:
print('[%s] change %d/%d not found'
@ -82,21 +120,41 @@ makes it available in your project's local working directory.
continue
if len(dl.commits) > 1:
print('[%s] %d/%d depends on %d unmerged changes:' \
print('[%s] %d/%d depends on %d unmerged changes:'
% (project.name, change_id, ps_id, len(dl.commits)),
file=sys.stderr)
for c in dl.commits:
print(' %s' % (c), file=sys.stderr)
if opt.cherrypick:
try:
project._CherryPick(dl.commit)
except GitError:
print('[%s] Could not complete the cherry-pick of %s' \
% (project.name, dl.commit), file=sys.stderr)
if opt.cherrypick:
mode = 'cherry-pick'
elif opt.revert:
project._Revert(dl.commit)
mode = 'revert'
elif opt.ffonly:
project._FastForward(dl.commit, ffonly=True)
mode = 'fast-forward merge'
else:
project._Checkout(dl.commit)
mode = 'checkout'
# We'll combine the branch+checkout operation, but all the rest need a
# dedicated branch start.
if opt.branch and mode != 'checkout':
project.StartBranch(opt.branch)
try:
if opt.cherrypick:
project._CherryPick(dl.commit, ffonly=opt.ffonly,
record_origin=opt.record_origin)
elif opt.revert:
project._Revert(dl.commit)
elif opt.ffonly:
project._FastForward(dl.commit, ffonly=True)
else:
if opt.branch:
project.StartBranch(opt.branch, revision=dl.commit)
else:
project._Checkout(dl.commit)
except GitError:
print('[%s] Could not complete the %s of %s'
% (project.name, mode, dl.commit), file=sys.stderr)
sys.exit(1)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,24 +12,28 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import fcntl
import errno
import functools
import io
import multiprocessing
import re
import os
import select
import signal
import sys
import subprocess
from color import Coloring
from command import Command, MirrorSafeCommand
from command import DEFAULT_LOCAL_JOBS, Command, MirrorSafeCommand, WORKER_BATCH_SIZE
from error import ManifestInvalidRevisionError
_CAN_COLOR = [
'branch',
'diff',
'grep',
'log',
'branch',
'diff',
'grep',
'log',
]
class ForallColoring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'forall')
@ -38,11 +41,11 @@ class ForallColoring(Coloring):
class Forall(Command, MirrorSafeCommand):
common = False
COMMON = False
helpSummary = "Run a shell command in each project"
helpUsage = """
%prog [<project>...] -c <command> [<arg>...]
%prog -r str1 [str2] ... -c <command> [<arg>...]"
%prog -r str1 [str2] ... -c <command> [<arg>...]
"""
helpDescription = """
Executes the same shell command in each project.
@ -50,8 +53,12 @@ Executes the same shell command in each project.
The -r option allows running the command only on projects matching
regex or wildcard expression.
Output Formatting
-----------------
By default, projects are processed non-interactively in parallel. If you want
to run interactive commands, make sure to pass --interactive to force --jobs 1.
While the processing order of projects is not guaranteed, the order of project
output is stable.
# Output Formatting
The -p option causes '%prog' to bind pipes to the command's stdin,
stdout and stderr streams, and pipe all output into a continuous
@ -68,8 +75,7 @@ command produces output only on stderr. Normally the -p option
causes command output to be suppressed until the command produces
at least one byte of output on stdout.
Environment
-----------
# Environment
pwd is the project's working directory. If the current client is
a mirror client, then pwd is the Git repository.
@ -78,6 +84,11 @@ REPO_PROJECT is set to the unique name of the project.
REPO_PATH is the path relative the the root of the client.
REPO_OUTERPATH is the path of the sub manifest's root relative to the root of
the client.
REPO_INNERPATH is the path relative to the root of the sub manifest.
REPO_REMOTE is the name of the remote system from the manifest.
REPO_LREV is the name of the revision from the manifest, translated
@ -101,46 +112,68 @@ annotating tree details.
shell positional arguments ($1, $2, .., $#) are set to any arguments
following <command>.
Example: to list projects:
%prog -c 'echo $REPO_PROJECT'
Notice that $REPO_PROJECT is quoted to ensure it is expanded in
the context of running <command> instead of in the calling shell.
Unless -p is used, stdin, stdout, stderr are inherited from the
terminal and are not redirected.
If -e is used, when a command exits unsuccessfully, '%prog' will abort
without iterating through the remaining projects.
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
@staticmethod
def _cmd_option(option, _opt_str, _value, parser):
setattr(parser.values, option.dest, list(parser.rargs))
while parser.rargs:
del parser.rargs[0]
def _Options(self, p):
def cmd(option, opt_str, value, parser):
setattr(parser.values, option.dest, list(parser.rargs))
while parser.rargs:
del parser.rargs[0]
p.add_option('-r', '--regex',
dest='regex', action='store_true',
help="Execute the command only on projects matching regex or wildcard expression")
help='execute the command only on projects matching regex or wildcard expression')
p.add_option('-i', '--inverse-regex',
dest='inverse_regex', action='store_true',
help='execute the command only on projects not matching regex or '
'wildcard expression')
p.add_option('-g', '--groups',
dest='groups',
help='execute the command only on projects matching the specified groups')
p.add_option('-c', '--command',
help='Command (and arguments) to execute',
help='command (and arguments) to execute',
dest='command',
action='callback',
callback=cmd)
callback=self._cmd_option)
p.add_option('-e', '--abort-on-errors',
dest='abort_on_errors', action='store_true',
help='Abort if a command exits unsuccessfully')
help='abort if a command exits unsuccessfully')
p.add_option('--ignore-missing', action='store_true',
help='silently skip & do not exit non-zero due missing '
'checkouts')
g = p.add_option_group('Output')
g = p.get_option_group('--quiet')
g.add_option('-p',
dest='project_header', action='store_true',
help='Show project headers before output')
g.add_option('-v', '--verbose',
dest='verbose', action='store_true',
help='Show command error messages')
help='show project headers before output')
p.add_option('--interactive',
action='store_true',
help='force interactive usage')
def WantPager(self, opt):
return opt.project_header
return opt.project_header and opt.jobs == 1
def Execute(self, opt, args):
def ValidateOptions(self, opt, args):
if not opt.command:
self.Usage()
def Execute(self, opt, args):
cmd = [opt.command[0]]
all_trees = not opt.this_manifest_only
shell = True
if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]):
@ -150,9 +183,14 @@ without iterating through the remaining projects.
cmd.append(cmd[0])
cmd.extend(opt.command[1:])
if opt.project_header \
and not shell \
and cmd[0] == 'git':
# Historically, forall operated interactively, and in serial. If the user
# has selected 1 job, then default to interacive mode.
if opt.jobs == 1:
opt.interactive = True
if opt.project_header \
and not shell \
and cmd[0] == 'git':
# If this is a direct git command that can enable colorized
# output and the user prefers coloring, add --color into the
# command line because we are going to wrap the command into
@ -163,136 +201,160 @@ without iterating through the remaining projects.
break
else:
cn = None
# pylint: disable=W0631
if cn and cn in _CAN_COLOR:
class ColorCmd(Coloring):
def __init__(self, config, cmd):
Coloring.__init__(self, config, cmd)
if ColorCmd(self.manifest.manifestProject.config, cn).is_on:
cmd.insert(cmd.index(cn) + 1, '--color')
# pylint: enable=W0631
mirror = self.manifest.IsMirror
out = ForallColoring(self.manifest.manifestProject.config)
out.redirect(sys.stdout)
rc = 0
first = True
if not opt.regex:
projects = self.GetProjects(args)
smart_sync_manifest_name = "smart_sync_override.xml"
smart_sync_manifest_path = os.path.join(
self.manifest.manifestProject.worktree, smart_sync_manifest_name)
if os.path.isfile(smart_sync_manifest_path):
self.manifest.Override(smart_sync_manifest_path)
if opt.regex:
projects = self.FindProjects(args, all_manifests=all_trees)
elif opt.inverse_regex:
projects = self.FindProjects(args, inverse=True, all_manifests=all_trees)
else:
projects = self.FindProjects(args)
projects = self.GetProjects(args, groups=opt.groups, all_manifests=all_trees)
os.environ['REPO_COUNT'] = str(len(projects))
for (cnt, project) in enumerate(projects):
env = os.environ.copy()
def setenv(name, val):
if val is None:
val = ''
env[name] = val.encode()
setenv('REPO_PROJECT', project.name)
setenv('REPO_PATH', project.relpath)
setenv('REPO_REMOTE', project.remote.name)
setenv('REPO_LREV', project.GetRevisionId())
setenv('REPO_RREV', project.revisionExpr)
setenv('REPO_I', str(cnt + 1))
for a in project.annotations:
setenv("REPO__%s" % (a.name), a.value)
if mirror:
setenv('GIT_DIR', project.gitdir)
cwd = project.gitdir
else:
cwd = project.worktree
if not os.path.exists(cwd):
if (opt.project_header and opt.verbose) \
or not opt.project_header:
print('skipping %s/' % project.relpath, file=sys.stderr)
continue
if opt.project_header:
stdin = subprocess.PIPE
stdout = subprocess.PIPE
stderr = subprocess.PIPE
else:
stdin = None
stdout = None
stderr = None
p = subprocess.Popen(cmd,
cwd = cwd,
shell = shell,
env = env,
stdin = stdin,
stdout = stdout,
stderr = stderr)
if opt.project_header:
class sfd(object):
def __init__(self, fd, dest):
self.fd = fd
self.dest = dest
def fileno(self):
return self.fd.fileno()
empty = True
errbuf = ''
p.stdin.close()
s_in = [sfd(p.stdout, sys.stdout),
sfd(p.stderr, sys.stderr)]
for s in s_in:
flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
while s_in:
in_ready, _out_ready, _err_ready = select.select(s_in, [], [])
for s in in_ready:
buf = s.fd.read(4096)
if not buf:
s.fd.close()
s_in.remove(s)
continue
if not opt.verbose:
if s.fd != p.stdout:
errbuf += buf
continue
if empty:
if first:
first = False
else:
out.nl()
if mirror:
project_header_path = project.name
else:
project_header_path = project.relpath
out.project('project %s/', project_header_path)
out.nl()
out.flush()
if errbuf:
sys.stderr.write(errbuf)
sys.stderr.flush()
errbuf = ''
empty = False
s.dest.write(buf)
s.dest.flush()
r = p.wait()
if r != 0:
if r != rc:
rc = r
if opt.abort_on_errors:
print("error: %s: Aborting due to previous error" % project.relpath,
file=sys.stderr)
sys.exit(r)
try:
config = self.manifest.manifestProject.config
with multiprocessing.Pool(opt.jobs, InitWorker) as pool:
results_it = pool.imap(
functools.partial(DoWorkWrapper, mirror, opt, cmd, shell, config),
enumerate(projects),
chunksize=WORKER_BATCH_SIZE)
first = True
for (r, output) in results_it:
if output:
if first:
first = False
elif opt.project_header:
print()
# To simplify the DoWorkWrapper, take care of automatic newlines.
end = '\n'
if output[-1] == '\n':
end = ''
print(output, end=end)
rc = rc or r
if r != 0 and opt.abort_on_errors:
raise Exception('Aborting due to previous error')
except (KeyboardInterrupt, WorkerKeyboardInterrupt):
# Catch KeyboardInterrupt raised inside and outside of workers
rc = rc or errno.EINTR
except Exception as e:
# Catch any other exceptions raised
print('forall: unhandled error, terminating the pool: %s: %s' %
(type(e).__name__, e),
file=sys.stderr)
rc = rc or getattr(e, 'errno', 1)
if rc != 0:
sys.exit(rc)
class WorkerKeyboardInterrupt(Exception):
""" Keyboard interrupt exception for worker processes. """
def InitWorker():
signal.signal(signal.SIGINT, signal.SIG_IGN)
def DoWorkWrapper(mirror, opt, cmd, shell, config, args):
""" A wrapper around the DoWork() method.
Catch the KeyboardInterrupt exceptions here and re-raise them as a different,
``Exception``-based exception to stop it flooding the console with stacktraces
and making the parent hang indefinitely.
"""
cnt, project = args
try:
return DoWork(project, mirror, opt, cmd, shell, cnt, config)
except KeyboardInterrupt:
print('%s: Worker interrupted' % project.name)
raise WorkerKeyboardInterrupt()
def DoWork(project, mirror, opt, cmd, shell, cnt, config):
env = os.environ.copy()
def setenv(name, val):
if val is None:
val = ''
env[name] = val
setenv('REPO_PROJECT', project.name)
setenv('REPO_OUTERPATH', project.manifest.path_prefix)
setenv('REPO_INNERPATH', project.relpath)
setenv('REPO_PATH', project.RelPath(local=opt.this_manifest_only))
setenv('REPO_REMOTE', project.remote.name)
try:
# If we aren't in a fully synced state and we don't have the ref the manifest
# wants, then this will fail. Ignore it for the purposes of this code.
lrev = '' if mirror else project.GetRevisionId()
except ManifestInvalidRevisionError:
lrev = ''
setenv('REPO_LREV', lrev)
setenv('REPO_RREV', project.revisionExpr)
setenv('REPO_UPSTREAM', project.upstream)
setenv('REPO_DEST_BRANCH', project.dest_branch)
setenv('REPO_I', str(cnt + 1))
for annotation in project.annotations:
setenv("REPO__%s" % (annotation.name), annotation.value)
if mirror:
setenv('GIT_DIR', project.gitdir)
cwd = project.gitdir
else:
cwd = project.worktree
if not os.path.exists(cwd):
# Allow the user to silently ignore missing checkouts so they can run on
# partial checkouts (good for infra recovery tools).
if opt.ignore_missing:
return (0, '')
output = ''
if ((opt.project_header and opt.verbose)
or not opt.project_header):
output = 'skipping %s/' % project.RelPath(local=opt.this_manifest_only)
return (1, output)
if opt.verbose:
stderr = subprocess.STDOUT
else:
stderr = subprocess.DEVNULL
stdin = None if opt.interactive else subprocess.DEVNULL
result = subprocess.run(
cmd, cwd=cwd, shell=shell, env=env, check=False,
encoding='utf-8', errors='replace',
stdin=stdin, stdout=subprocess.PIPE, stderr=stderr)
output = result.stdout
if opt.project_header:
if output:
buf = io.StringIO()
out = ForallColoring(config)
out.redirect(buf)
if mirror:
project_header_path = project.name
else:
project_header_path = project.RelPath(local=opt.this_manifest_only)
out.project('project %s/' % project_header_path)
out.nl()
buf.write(output)
output = buf.getvalue()
return (result.returncode, output)

46
subcmds/gitc_delete.py Normal file
View File

@ -0,0 +1,46 @@
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from command import Command, GitcClientCommand
import platform_utils
class GitcDelete(Command, GitcClientCommand):
COMMON = True
visible_everywhere = False
helpSummary = "Delete a GITC Client."
helpUsage = """
%prog
"""
helpDescription = """
This subcommand deletes the current GITC client, deleting the GITC manifest
and all locally downloaded sources.
"""
def _Options(self, p):
p.add_option('-f', '--force',
dest='force', action='store_true',
help='force the deletion (no prompt)')
def Execute(self, opt, args):
if not opt.force:
prompt = ('This will delete GITC client: %s\nAre you sure? (yes/no) ' %
self.gitc_manifest.gitc_client_name)
response = input(prompt).lower()
if not response == 'yes':
print('Response was not "yes"\n Exiting...')
sys.exit(1)
platform_utils.rmtree(self.gitc_manifest.gitc_client_dir)

75
subcmds/gitc_init.py Normal file
View File

@ -0,0 +1,75 @@
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import gitc_utils
from command import GitcAvailableCommand
from manifest_xml import GitcManifest
from subcmds import init
import wrapper
class GitcInit(init.Init, GitcAvailableCommand):
COMMON = True
MULTI_MANIFEST_SUPPORT = False
helpSummary = "Initialize a GITC Client."
helpUsage = """
%prog [options] [client name]
"""
helpDescription = """
The '%prog' command is ran to initialize a new GITC client for use
with the GITC file system.
This command will setup the client directory, initialize repo, just
like repo init does, and then downloads the manifest collection
and installs it in the .repo/directory of the GITC client.
Once this is done, a GITC manifest is generated by pulling the HEAD
SHA for each project and generates the properly formatted XML file
and installs it as .manifest in the GITC client directory.
The -c argument is required to specify the GITC client name.
The optional -f argument can be used to specify the manifest file to
use for this GITC client.
"""
def _Options(self, p):
super()._Options(p, gitc_init=True)
def Execute(self, opt, args):
gitc_client = gitc_utils.parse_clientdir(os.getcwd())
if not gitc_client or (opt.gitc_client and gitc_client != opt.gitc_client):
print('fatal: Please update your repo command. See go/gitc for instructions.',
file=sys.stderr)
sys.exit(1)
self.client_dir = os.path.join(gitc_utils.get_gitc_manifest_dir(),
gitc_client)
super().Execute(opt, args)
manifest_file = self.manifest.manifestFile
if opt.manifest_file:
if not os.path.exists(opt.manifest_file):
print('fatal: Specified manifest file %s does not exist.' %
opt.manifest_file)
sys.exit(1)
manifest_file = opt.manifest_file
manifest = GitcManifest(self.repodir, gitc_client)
manifest.Override(manifest_file)
gitc_utils.generate_gitc_manifest(None, manifest)
print('Please run `cd %s` to view your GITC client.' %
os.path.join(wrapper.Wrapper().GITC_FS_ROOT_DIR, gitc_client))

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,19 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import functools
import sys
from color import Coloring
from command import PagedCommand
from git_command import git_require, GitCommand
from command import DEFAULT_LOCAL_JOBS, PagedCommand
from error import GitError
from git_command import GitCommand
class GrepColoring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'grep')
self.project = self.printer('project', attr='bold')
self.fail = self.printer('fail', fg='red')
class Grep(PagedCommand):
common = True
COMMON = True
helpSummary = "Print lines matching a pattern"
helpUsage = """
%prog {pattern | -e pattern} [<project>...]
@ -33,8 +37,7 @@ class Grep(PagedCommand):
helpDescription = """
Search for the specified patterns in all project files.
Boolean Options
---------------
# Boolean Options
The following options can appear as often as necessary to express
the pattern to locate:
@ -47,8 +50,7 @@ in order to scan multiple trees. If the same file matches in more
than one tree, only the first result is reported, prefixed by the
revision name it was found under.
Examples
-------
# Examples
Look for a line that has '#define' and either 'MAX_PATH or 'PATH_MAX':
@ -60,30 +62,33 @@ contain a line that matches both expressions:
repo grep --all-match -e NODE -e Unexpected
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
@staticmethod
def _carry_option(_option, opt_str, value, parser):
pt = getattr(parser.values, 'cmd_argv', None)
if pt is None:
pt = []
setattr(parser.values, 'cmd_argv', pt)
if opt_str == '-(':
pt.append('(')
elif opt_str == '-)':
pt.append(')')
else:
pt.append(opt_str)
if value is not None:
pt.append(value)
def _CommonOptions(self, p):
"""Override common options slightly."""
super()._CommonOptions(p, opt_v=False)
def _Options(self, p):
def carry(option,
opt_str,
value,
parser):
pt = getattr(parser.values, 'cmd_argv', None)
if pt is None:
pt = []
setattr(parser.values, 'cmd_argv', pt)
if opt_str == '-(':
pt.append('(')
elif opt_str == '-)':
pt.append(')')
else:
pt.append(opt_str)
if value is not None:
pt.append(value)
g = p.add_option_group('Sources')
g.add_option('--cached',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Search the index, instead of the work tree')
g.add_option('-r', '--revision',
dest='revision', action='append', metavar='TREEish',
@ -91,74 +96,140 @@ contain a line that matches both expressions:
g = p.add_option_group('Pattern')
g.add_option('-e',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
metavar='PATTERN', type='str',
help='Pattern to search for')
g.add_option('-i', '--ignore-case',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Ignore case differences')
g.add_option('-a', '--text',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help="Process binary files as if they were text")
g.add_option('-I',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help="Don't match the pattern in binary files")
g.add_option('-w', '--word-regexp',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Match the pattern only at word boundaries')
g.add_option('-v', '--invert-match',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Select non-matching lines')
g.add_option('-G', '--basic-regexp',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Use POSIX basic regexp for patterns (default)')
g.add_option('-E', '--extended-regexp',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Use POSIX extended regexp for patterns')
g.add_option('-F', '--fixed-strings',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Use fixed strings (not regexp) for pattern')
g = p.add_option_group('Pattern Grouping')
g.add_option('--all-match',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Limit match to lines that have all patterns')
g.add_option('--and', '--or', '--not',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Boolean operators to combine patterns')
g.add_option('-(', '-)',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Boolean operator grouping')
g = p.add_option_group('Output')
g.add_option('-n',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Prefix the line number to matching lines')
g.add_option('-C',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
metavar='CONTEXT', type='str',
help='Show CONTEXT lines around match')
g.add_option('-B',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
metavar='CONTEXT', type='str',
help='Show CONTEXT lines before match')
g.add_option('-A',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
metavar='CONTEXT', type='str',
help='Show CONTEXT lines after match')
g.add_option('-l', '--name-only', '--files-with-matches',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Show only file names containing matching lines')
g.add_option('-L', '--files-without-match',
action='callback', callback=carry,
action='callback', callback=self._carry_option,
help='Show only file names not containing matching lines')
def _ExecuteOne(self, cmd_argv, project):
"""Process one project."""
try:
p = GitCommand(project,
cmd_argv,
bare=False,
capture_stdout=True,
capture_stderr=True)
except GitError as e:
return (project, -1, None, str(e))
return (project, p.Wait(), p.stdout, p.stderr)
@staticmethod
def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
git_failed = False
bad_rev = False
have_match = False
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
for project, rc, stdout, stderr in results:
if rc < 0:
git_failed = True
out.project('--- project %s ---' % _RelPath(project))
out.nl()
out.fail('%s', stderr)
out.nl()
continue
if rc:
# no results
if stderr:
if have_rev and 'fatal: ambiguous argument' in stderr:
bad_rev = True
else:
out.project('--- project %s ---' % _RelPath(project))
out.nl()
out.fail('%s', stderr.strip())
out.nl()
continue
have_match = True
# We cut the last element, to avoid a blank line.
r = stdout.split('\n')
r = r[0:-1]
if have_rev and full_name:
for line in r:
rev, line = line.split(':', 1)
out.write("%s", rev)
out.write(':')
out.project(_RelPath(project))
out.write('/')
out.write("%s", line)
out.nl()
elif full_name:
for line in r:
out.project(_RelPath(project))
out.write('/')
out.write("%s", line)
out.nl()
else:
for line in r:
print(line)
return (git_failed, bad_rev, have_match)
def Execute(self, opt, args):
out = GrepColoring(self.manifest.manifestProject.config)
cmd_argv = ['grep']
if out.is_on and git_require((1, 6, 3)):
if out.is_on:
cmd_argv.append('--color')
cmd_argv.extend(getattr(opt, 'cmd_argv', []))
@ -169,7 +240,7 @@ contain a line that matches both expressions:
cmd_argv.append(args[0])
args = args[1:]
projects = self.GetProjects(args)
projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
full_name = False
if len(projects) > 1:
@ -185,54 +256,17 @@ contain a line that matches both expressions:
cmd_argv.extend(opt.revision)
cmd_argv.append('--')
bad_rev = False
have_match = False
git_failed, bad_rev, have_match = self.ExecuteInParallel(
opt.jobs,
functools.partial(self._ExecuteOne, cmd_argv),
projects,
callback=functools.partial(self._ProcessResults, full_name, have_rev, opt),
output=out,
ordered=True)
for project in projects:
p = GitCommand(project,
cmd_argv,
bare = False,
capture_stdout = True,
capture_stderr = True)
if p.Wait() != 0:
# no results
#
if p.stderr:
if have_rev and 'fatal: ambiguous argument' in p.stderr:
bad_rev = True
else:
out.project('--- project %s ---' % project.relpath)
out.nl()
out.write("%s", p.stderr)
out.nl()
continue
have_match = True
# We cut the last element, to avoid a blank line.
#
r = p.stdout.split('\n')
r = r[0:-1]
if have_rev and full_name:
for line in r:
rev, line = line.split(':', 1)
out.write("%s", rev)
out.write(':')
out.project(project.relpath)
out.write('/')
out.write("%s", line)
out.nl()
elif full_name:
for line in r:
out.project(project.relpath)
out.write('/')
out.write("%s", line)
out.nl()
else:
for line in r:
print(line)
if have_match:
if git_failed:
sys.exit(1)
elif have_match:
sys.exit(0)
elif have_rev and bad_rev:
for r in opt.revision:

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,16 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import re
import sys
from formatter import AbstractFormatter, DumbWriter
import textwrap
from subcmds import all_commands
from color import Coloring
from command import PagedCommand, MirrorSafeCommand
from command import PagedCommand, MirrorSafeCommand, GitcAvailableCommand, GitcClientCommand
import gitc_utils
from wrapper import Wrapper
class Help(PagedCommand, MirrorSafeCommand):
common = False
COMMON = False
helpSummary = "Display detailed help on a command"
helpUsage = """
%prog [--all|command]
@ -31,56 +33,67 @@ class Help(PagedCommand, MirrorSafeCommand):
Displays detailed usage information about a command.
"""
def _PrintAllCommands(self):
print('usage: repo COMMAND [ARGS]')
print('The complete list of recognized repo commands are:')
commandNames = list(sorted(self.commands))
def _PrintCommands(self, commandNames):
"""Helper to display |commandNames| summaries."""
maxlen = 0
for name in commandNames:
maxlen = max(maxlen, len(name))
fmt = ' %%-%ds %%s' % maxlen
for name in commandNames:
command = self.commands[name]
command = all_commands[name]()
try:
summary = command.helpSummary.strip()
except AttributeError:
summary = ''
print(fmt % (name, summary))
def _PrintAllCommands(self):
print('usage: repo COMMAND [ARGS]')
self.PrintAllCommandsBody()
def PrintAllCommandsBody(self):
print('The complete list of recognized repo commands is:')
commandNames = list(sorted(all_commands))
self._PrintCommands(commandNames)
print("See 'repo help <command>' for more information on a "
'specific command.')
print('Bug reports:', Wrapper().BUG_URL)
def _PrintCommonCommands(self):
print('usage: repo COMMAND [ARGS]')
self.PrintCommonCommandsBody()
def PrintCommonCommandsBody(self):
print('The most commonly used repo commands are:')
def gitc_supported(cmd):
if not isinstance(cmd, GitcAvailableCommand) and not isinstance(cmd, GitcClientCommand):
return True
if self.client.isGitcClient:
return True
if isinstance(cmd, GitcClientCommand):
return False
if gitc_utils.get_gitc_manifest_dir():
return True
return False
commandNames = list(sorted([name
for name, command in self.commands.items()
if command.common]))
for name, command in all_commands.items()
if command.COMMON and gitc_supported(command)]))
self._PrintCommands(commandNames)
maxlen = 0
for name in commandNames:
maxlen = max(maxlen, len(name))
fmt = ' %%-%ds %%s' % maxlen
for name in commandNames:
command = self.commands[name]
try:
summary = command.helpSummary.strip()
except AttributeError:
summary = ''
print(fmt % (name, summary))
print(
"See 'repo help <command>' for more information on a specific command.\n"
"See 'repo help --all' for a complete list of recognized commands.")
"See 'repo help <command>' for more information on a specific command.\n"
"See 'repo help --all' for a complete list of recognized commands.")
print('Bug reports:', Wrapper().BUG_URL)
def _PrintCommandHelp(self, cmd):
def _PrintCommandHelp(self, cmd, header_prefix=''):
class _Out(Coloring):
def __init__(self, gc):
Coloring.__init__(self, gc, 'help')
self.heading = self.printer('heading', attr='bold')
self.wrap = AbstractFormatter(DumbWriter())
self._first = True
def _PrintSection(self, heading, bodyAttr):
try:
@ -90,19 +103,20 @@ Displays detailed usage information about a command.
if body == '' or body is None:
return
self.nl()
if not self._first:
self.nl()
self._first = False
self.heading('%s', heading)
self.heading('%s%s', header_prefix, heading)
self.nl()
self.heading('%s', ''.ljust(len(heading), '-'))
self.nl()
me = 'repo %s' % cmd.NAME
body = body.strip()
body = body.replace('%prog', me)
asciidoc_hdr = re.compile(r'^\n?([^\n]{1,})\n([=~-]{2,})$')
# Extract the title, but skip any trailing {#anchors}.
asciidoc_hdr = re.compile(r'^\n?#+ ([^{]+)(\{#.+\})?$')
for para in body.split("\n\n"):
if para.startswith(' '):
self.write('%s', para)
@ -112,39 +126,41 @@ Displays detailed usage information about a command.
m = asciidoc_hdr.match(para)
if m:
title = m.group(1)
section_type = m.group(2)
if section_type[0] in ('=', '-'):
p = self.heading
else:
def _p(fmt, *args):
self.write(' ')
self.heading(fmt, *args)
p = _p
p('%s', title)
self.heading('%s%s', header_prefix, m.group(1))
self.nl()
p('%s', ''.ljust(len(title), section_type[0]))
self.nl()
continue
self.wrap.add_flowing_data(para)
self.wrap.end_paragraph(1)
self.wrap.end_paragraph(0)
lines = textwrap.wrap(para.replace(' ', ' '), width=80,
break_long_words=False, break_on_hyphens=False)
for line in lines:
self.write('%s', line)
self.nl()
self.nl()
out = _Out(self.manifest.globalConfig)
out = _Out(self.client.globalConfig)
out._PrintSection('Summary', 'helpSummary')
cmd.OptionParser.print_help()
out._PrintSection('Description', 'helpDescription')
def _PrintAllCommandHelp(self):
for name in sorted(all_commands):
cmd = all_commands[name](manifest=self.manifest)
self._PrintCommandHelp(cmd, header_prefix='[%s] ' % (name,))
def _Options(self, p):
p.add_option('-a', '--all',
dest='show_all', action='store_true',
help='show the complete list of commands')
p.add_option('--help-all',
dest='show_all_help', action='store_true',
help='show the --help of all commands')
def Execute(self, opt, args):
if len(args) == 0:
if opt.show_all:
if opt.show_all_help:
self._PrintAllCommandHelp()
elif opt.show_all:
self._PrintAllCommands()
else:
self._PrintCommonCommands()
@ -153,12 +169,11 @@ Displays detailed usage information about a command.
name = args[0]
try:
cmd = self.commands[name]
cmd = all_commands[name](manifest=self.manifest)
except KeyError:
print("repo: '%s' is not a repo command." % name, file=sys.stderr)
sys.exit(1)
cmd.manifest = self.manifest
self._PrintCommandHelp(cmd)
else:

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,19 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
from command import PagedCommand
from color import Coloring
from error import NoSuchProjectError
from git_refs import R_M
from git_refs import R_M, R_HEADS
class _Coloring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, "status")
class Info(PagedCommand):
common = True
COMMON = True
helpSummary = "Get info on the manifest branch, current branch or unmerged branches"
helpUsage = "%prog [-dl] [-o [-b]] [<project>...]"
helpUsage = "%prog [-dl] [-o [-c]] [<project>...]"
def _Options(self, p):
p.add_option('-d', '--diff',
@ -34,32 +36,40 @@ class Info(PagedCommand):
p.add_option('-o', '--overview',
dest='overview', action='store_true',
help='show overview of all local commits')
p.add_option('-b', '--current-branch',
p.add_option('-c', '--current-branch',
dest="current_branch", action="store_true",
help="consider only checked out branches")
p.add_option('--no-current-branch',
dest='current_branch', action='store_false',
help='consider all local branches')
# Turn this into a warning & remove this someday.
p.add_option('-b',
dest='current_branch', action='store_true',
help=optparse.SUPPRESS_HELP)
p.add_option('-l', '--local-only',
dest="local", action="store_true",
help="Disable all remote operations")
help="disable all remote operations")
def Execute(self, opt, args):
self.out = _Coloring(self.manifest.globalConfig)
self.heading = self.out.printer('heading', attr = 'bold')
self.headtext = self.out.printer('headtext', fg = 'yellow')
self.redtext = self.out.printer('redtext', fg = 'red')
self.sha = self.out.printer("sha", fg = 'yellow')
self.out = _Coloring(self.client.globalConfig)
self.heading = self.out.printer('heading', attr='bold')
self.headtext = self.out.nofmt_printer('headtext', fg='yellow')
self.redtext = self.out.printer('redtext', fg='red')
self.sha = self.out.printer("sha", fg='yellow')
self.text = self.out.nofmt_printer('text')
self.dimtext = self.out.printer('dimtext', attr = 'dim')
self.dimtext = self.out.printer('dimtext', attr='dim')
self.opt = opt
if not opt.this_manifest_only:
self.manifest = self.manifest.outer_client
manifestConfig = self.manifest.manifestProject.config
mergeBranch = manifestConfig.GetBranch("default").merge
manifestGroups = (manifestConfig.GetString('manifest.groups')
or 'all,-notdefault')
manifestGroups = self.manifest.GetGroupsStr()
self.heading("Manifest branch: ")
self.headtext(self.manifest.default.revisionExpr)
if self.manifest.default.revisionExpr:
self.headtext(self.manifest.default.revisionExpr)
self.out.nl()
self.heading("Manifest merge branch: ")
self.headtext(mergeBranch)
@ -71,19 +81,17 @@ class Info(PagedCommand):
self.printSeparator()
if not opt.overview:
self.printDiffInfo(args)
self._printDiffInfo(opt, args)
else:
self.printCommitOverview(args)
self._printCommitOverview(opt, args)
def printSeparator(self):
self.text("----------------------------")
self.out.nl()
def printDiffInfo(self, args):
try:
projs = self.GetProjects(args)
except NoSuchProjectError:
return
def _printDiffInfo(self, opt, args):
# We let exceptions bubble up to main as they'll be well structured.
projs = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
for p in projs:
self.heading("Project: ")
@ -95,13 +103,23 @@ class Info(PagedCommand):
self.out.nl()
self.heading("Current revision: ")
self.headtext(p.GetRevisionId())
self.out.nl()
currentBranch = p.CurrentBranch
if currentBranch:
self.heading('Current branch: ')
self.headtext(currentBranch)
self.out.nl()
self.heading("Manifest revision: ")
self.headtext(p.revisionExpr)
self.out.nl()
localBranches = p.GetBranches().keys()
localBranches = list(p.GetBranches().keys())
self.heading("Local Branches: ")
self.redtext(str(len(localBranches)))
if len(localBranches) > 0:
if localBranches:
self.text(" [")
self.text(", ".join(localBranches))
self.text("]")
@ -113,11 +131,14 @@ class Info(PagedCommand):
self.printSeparator()
def findRemoteLocalDiff(self, project):
#Fetch all the latest commits
# Fetch all the latest commits.
if not self.opt.local:
project.Sync_NetworkHalf(quiet=True, current_branch_only=True)
logTarget = R_M + self.manifest.manifestProject.config.GetBranch("default").merge
branch = self.manifest.manifestProject.config.GetBranch('default').merge
if branch.startswith(R_HEADS):
branch = branch[len(R_HEADS):]
logTarget = R_M + branch
bareTmp = project.bare_git._bare
project.bare_git._bare = False
@ -159,9 +180,9 @@ class Info(PagedCommand):
self.text(" ".join(split[1:]))
self.out.nl()
def printCommitOverview(self, args):
def _printCommitOverview(self, opt, args):
all_branches = []
for project in self.GetProjects(args):
for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only):
br = [project.GetUploadableBranch(x)
for x in project.GetBranches()]
br = [x for x in br if x]
@ -180,22 +201,22 @@ class Info(PagedCommand):
if project != branch.project:
project = branch.project
self.out.nl()
self.headtext(project.relpath)
self.headtext(project.RelPath(local=opt.this_manifest_only))
self.out.nl()
commits = branch.commits
date = branch.date
self.text('%s %-33s (%2d commit%s, %s)' % (
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
len(commits),
len(commits) != 1 and 's' or '',
date))
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
len(commits),
len(commits) != 1 and 's' or '',
date))
self.out.nl()
for commit in commits:
split = commit.split()
self.text('{0:38}{1} '.format('','-'))
self.text('{0:38}{1} '.format('', '-'))
self.sha(split[0] + " ")
self.text(" ".join(split[1:]))
self.out.nl()

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,34 +12,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import platform
import re
import shutil
import sys
from pyversion import is_python3
if is_python3():
import urllib.parse
else:
import imp
import urlparse
urllib = imp.new_module('urllib')
urllib.parse = urlparse.urlparse
import urllib.parse
from color import Coloring
from command import InteractiveCommand, MirrorSafeCommand
from error import ManifestParseError
from project import SyncBuffer
from git_config import GitConfig
from git_command import git_require, MIN_GIT_VERSION
from git_command import git_require, MIN_GIT_VERSION_SOFT, MIN_GIT_VERSION_HARD
from wrapper import Wrapper
class Init(InteractiveCommand, MirrorSafeCommand):
common = True
helpSummary = "Initialize repo in the current directory"
COMMON = True
MULTI_MANIFEST_SUPPORT = True
helpSummary = "Initialize a repo client checkout in the current directory"
helpUsage = """
%prog [options]
%prog [options] [manifest url]
"""
helpDescription = """
The '%prog' command is run once to install and initialize repo.
@ -48,22 +40,42 @@ The latest repo source code and manifest collection is downloaded
from the server and is installed in the .repo/ directory in the
current working directory.
When creating a new checkout, the manifest URL is the only required setting.
It may be specified using the --manifest-url option, or as the first optional
argument.
The optional -b argument can be used to select the manifest branch
to checkout and use. If no branch is specified, master is assumed.
to checkout and use. If no branch is specified, the remote's default
branch is used. This is equivalent to using -b HEAD.
The optional -m argument can be used to specify an alternate manifest
to be used. If no manifest is specified, the manifest default.xml
will be used.
If the --standalone-manifest argument is set, the manifest will be downloaded
directly from the specified --manifest-url as a static file (rather than
setting up a manifest git checkout). With --standalone-manifest, the manifest
will be fully static and will not be re-downloaded during subsesquent
`repo init` and `repo sync` calls.
The --reference option can be used to point to a directory that
has the content of a --mirror sync. This will make the working
directory use as much data as possible from the local reference
directory when fetching from the server. This will make the sync
go a lot faster by reducing data traffic on the network.
The --dissociate option can be used to borrow the objects from
the directory specified with the --reference option only to reduce
network transfer, and stop borrowing from them after a first clone
is made by making necessary local copies of borrowed objects.
Switching Manifest Branches
---------------------------
The --no-clone-bundle option disables any attempt to use
$URL/clone.bundle to bootstrap a new Git repository from a
resumeable bundle file on a content delivery network. This
may be necessary if there are problems with the local Python
HTTP client or proxy configuration, but the Git binary works.
# Switching Manifest Branches
To switch to another manifest branch, `repo init -b otherbranch`
may be used in an existing client. However, as this only updates the
@ -71,200 +83,73 @@ manifest, a subsequent `repo sync` (or `repo sync -d`) is necessary
to update the working directory files.
"""
def _Options(self, p):
# Logging
g = p.add_option_group('Logging options')
g.add_option('-q', '--quiet',
dest="quiet", action="store_true", default=False,
help="be quiet")
def _CommonOptions(self, p):
"""Disable due to re-use of Wrapper()."""
# Manifest
g = p.add_option_group('Manifest options')
g.add_option('-u', '--manifest-url',
dest='manifest_url',
help='manifest repository location', metavar='URL')
g.add_option('-b', '--manifest-branch',
dest='manifest_branch',
help='manifest branch or revision', metavar='REVISION')
g.add_option('-m', '--manifest-name',
dest='manifest_name', default='default.xml',
help='initial manifest file', metavar='NAME.xml')
g.add_option('--mirror',
dest='mirror', action='store_true',
help='create a replica of the remote repositories '
'rather than a client working directory')
g.add_option('--reference',
dest='reference',
help='location of mirror directory', metavar='DIR')
g.add_option('--depth', type='int', default=None,
dest='depth',
help='create a shallow clone with given depth; see git clone')
g.add_option('--archive',
dest='archive', action='store_true',
help='checkout an archive instead of a git repository for '
'each project. See git archive.')
g.add_option('-g', '--groups',
dest='groups', default='default',
help='restrict manifest projects to ones with specified '
'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]',
metavar='GROUP')
g.add_option('-p', '--platform',
dest='platform', default='auto',
help='restrict manifest projects to ones with a specified '
'platform group [auto|all|none|linux|darwin|...]',
metavar='PLATFORM')
# Tool
g = p.add_option_group('repo Version options')
g.add_option('--repo-url',
dest='repo_url',
help='repo repository location', metavar='URL')
g.add_option('--repo-branch',
dest='repo_branch',
help='repo branch or revision', metavar='REVISION')
g.add_option('--no-repo-verify',
dest='no_repo_verify', action='store_true',
help='do not verify repo source code')
# Other
g = p.add_option_group('Other options')
g.add_option('--config-name',
dest='config_name', action="store_true", default=False,
help='Always prompt for name/e-mail')
def _Options(self, p, gitc_init=False):
Wrapper().InitParser(p, gitc_init=gitc_init)
m = p.add_option_group('Multi-manifest')
m.add_option('--outer-manifest', action='store_true', default=True,
help='operate starting at the outermost manifest')
m.add_option('--no-outer-manifest', dest='outer_manifest',
action='store_false', help='do not operate on outer manifests')
m.add_option('--this-manifest-only', action='store_true', default=None,
help='only operate on this (sub)manifest')
m.add_option('--no-this-manifest-only', '--all-manifests',
dest='this_manifest_only', action='store_false',
help='operate on this manifest and its submanifests')
def _RegisteredEnvironmentOptions(self):
return {'REPO_MANIFEST_URL': 'manifest_url',
'REPO_MIRROR_LOCATION': 'reference'}
def _SyncManifest(self, opt):
m = self.manifest.manifestProject
is_new = not m.Exists
"""Call manifestProject.Sync with arguments from opt.
if is_new:
if not opt.manifest_url:
print('fatal: manifest url (-u) is required.', file=sys.stderr)
sys.exit(1)
if not opt.quiet:
print('Get %s' % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url),
file=sys.stderr)
# The manifest project object doesn't keep track of the path on the
# server where this git is located, so let's save that here.
mirrored_manifest_git = None
if opt.reference:
manifest_git_path = urllib.parse(opt.manifest_url).path[1:]
mirrored_manifest_git = os.path.join(opt.reference, manifest_git_path)
if not mirrored_manifest_git.endswith(".git"):
mirrored_manifest_git += ".git"
if not os.path.exists(mirrored_manifest_git):
mirrored_manifest_git = os.path.join(opt.reference + '/.repo/manifests.git')
m._InitGitDir(mirror_git=mirrored_manifest_git)
if opt.manifest_branch:
m.revisionExpr = opt.manifest_branch
else:
m.revisionExpr = 'refs/heads/master'
else:
if opt.manifest_branch:
m.revisionExpr = opt.manifest_branch
else:
m.PreSync()
if opt.manifest_url:
r = m.GetRemote(m.remote.name)
r.url = opt.manifest_url
r.ResetFetch()
r.Save()
groups = re.split(r'[,\s]+', opt.groups)
all_platforms = ['linux', 'darwin']
platformize = lambda x: 'platform-' + x
if opt.platform == 'auto':
if (not opt.mirror and
not m.config.GetString('repo.mirror') == 'true'):
groups.append(platformize(platform.system().lower()))
elif opt.platform == 'all':
groups.extend(map(platformize, all_platforms))
elif opt.platform in all_platforms:
groups.extend(platformize(opt.platform))
elif opt.platform != 'none':
print('fatal: invalid platform flag', file=sys.stderr)
sys.exit(1)
groups = [x for x in groups if x]
groupstr = ','.join(groups)
if opt.platform == 'auto' and groupstr == 'default,platform-' + platform.system().lower():
groupstr = None
m.config.SetString('manifest.groups', groupstr)
if opt.reference:
m.config.SetString('repo.reference', opt.reference)
if opt.archive:
if is_new:
m.config.SetString('repo.archive', 'true')
else:
print('fatal: --archive is only supported when initializing a new '
'workspace.', file=sys.stderr)
print('Either delete the .repo folder in this workspace, or initialize '
'in another location.', file=sys.stderr)
sys.exit(1)
if opt.mirror:
if is_new:
m.config.SetString('repo.mirror', 'true')
else:
print('fatal: --mirror is only supported when initializing a new '
'workspace.', file=sys.stderr)
print('Either delete the .repo folder in this workspace, or initialize '
'in another location.', file=sys.stderr)
sys.exit(1)
if not m.Sync_NetworkHalf(is_new=is_new):
r = m.GetRemote(m.remote.name)
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
# Better delete the manifest git dir if we created it; otherwise next
# time (when user fixes problems) we won't go through the "is_new" logic.
if is_new:
shutil.rmtree(m.gitdir)
sys.exit(1)
if opt.manifest_branch:
m.MetaBranchSwitch(opt.manifest_branch)
syncbuf = SyncBuffer(m.config)
m.Sync_LocalHalf(syncbuf)
syncbuf.Finish()
if is_new or m.CurrentBranch is None:
if not m.StartBranch('default'):
print('fatal: cannot create default in manifest', file=sys.stderr)
sys.exit(1)
def _LinkManifest(self, name):
if not name:
print('fatal: manifest name (-m) is required.', file=sys.stderr)
sys.exit(1)
try:
self.manifest.Link(name)
except ManifestParseError as e:
print("fatal: manifest '%s' not available" % name, file=sys.stderr)
print('fatal: %s' % str(e), file=sys.stderr)
Args:
opt: options from optparse.
"""
# Normally this value is set when instantiating the project, but the
# manifest project is special and is created when instantiating the
# manifest which happens before we parse options.
self.manifest.manifestProject.clone_depth = opt.manifest_depth
if not self.manifest.manifestProject.Sync(
manifest_url=opt.manifest_url,
manifest_branch=opt.manifest_branch,
standalone_manifest=opt.standalone_manifest,
groups=opt.groups,
platform=opt.platform,
mirror=opt.mirror,
dissociate=opt.dissociate,
reference=opt.reference,
worktree=opt.worktree,
submodules=opt.submodules,
archive=opt.archive,
partial_clone=opt.partial_clone,
clone_filter=opt.clone_filter,
partial_clone_exclude=opt.partial_clone_exclude,
clone_bundle=opt.clone_bundle,
git_lfs=opt.git_lfs,
use_superproject=opt.use_superproject,
verbose=opt.verbose,
current_branch_only=opt.current_branch_only,
tags=opt.tags,
depth=opt.depth,
git_event_log=self.git_event_log,
manifest_name=opt.manifest_name):
sys.exit(1)
def _Prompt(self, prompt, value):
sys.stdout.write('%-10s [%s]: ' % (prompt, value))
print('%-10s [%s]: ' % (prompt, value), end='')
# TODO: When we require Python 3, use flush=True w/print above.
sys.stdout.flush()
a = sys.stdin.readline().strip()
if a == '':
return value
return a
def _ShouldConfigureUser(self):
gc = self.manifest.globalConfig
def _ShouldConfigureUser(self, opt, existing_checkout):
gc = self.client.globalConfig
mp = self.manifest.manifestProject
# If we don't have local settings, get from global.
@ -275,23 +160,28 @@ to update the working directory files.
mp.config.SetString('user.name', gc.GetString('user.name'))
mp.config.SetString('user.email', gc.GetString('user.email'))
print()
print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
mp.config.GetString('user.email')))
print('If you want to change this, please re-run \'repo init\' with --config-name')
if not opt.quiet and not existing_checkout or opt.verbose:
print()
print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
mp.config.GetString('user.email')))
print("If you want to change this, please re-run 'repo init' with --config-name")
return False
def _ConfigureUser(self):
def _ConfigureUser(self, opt):
mp = self.manifest.manifestProject
while True:
print()
name = self._Prompt('Your Name', mp.UserName)
if not opt.quiet:
print()
name = self._Prompt('Your Name', mp.UserName)
email = self._Prompt('Your Email', mp.UserEmail)
print()
if not opt.quiet:
print()
print('Your identity is: %s <%s>' % (name, email))
sys.stdout.write('is this correct [y/N]? ')
print('is this correct [y/N]? ', end='')
# TODO: When we require Python 3, use flush=True w/print above.
sys.stdout.flush()
a = sys.stdin.readline().strip().lower()
if a in ('yes', 'y', 't', 'true'):
break
@ -308,7 +198,7 @@ to update the working directory files.
return False
def _ConfigureColor(self):
gc = self.manifest.globalConfig
gc = self.client.globalConfig
if self._HasColorSet(gc):
return
@ -333,68 +223,110 @@ to update the working directory files.
out.printer(fg='black', attr=c)(' %-6s ', c)
out.nl()
sys.stdout.write('Enable color display in this user account (y/N)? ')
print('Enable color display in this user account (y/N)? ', end='')
# TODO: When we require Python 3, use flush=True w/print above.
sys.stdout.flush()
a = sys.stdin.readline().strip().lower()
if a in ('y', 'yes', 't', 'true', 'on'):
gc.SetString('color.ui', 'auto')
def _ConfigureDepth(self, opt):
"""Configure the depth we'll sync down.
Args:
opt: Options from optparse. We care about opt.depth.
"""
# Opt.depth will be non-None if user actually passed --depth to repo init.
if opt.depth is not None:
if opt.depth > 0:
# Positive values will set the depth.
depth = str(opt.depth)
else:
# Negative numbers will clear the depth; passing None to SetString
# will do that.
depth = None
# We store the depth in the main manifest project.
self.manifest.manifestProject.config.SetString('repo.depth', depth)
def _DisplayResult(self):
def _DisplayResult(self, opt):
if self.manifest.IsMirror:
init_type = 'mirror '
else:
init_type = ''
print()
print('repo %shas been initialized in %s'
% (init_type, self.manifest.topdir))
if not opt.quiet:
print()
print('repo %shas been initialized in %s' %
(init_type, self.manifest.topdir))
current_dir = os.getcwd()
if current_dir != self.manifest.topdir:
print('If this is not the directory in which you want to initialize '
'repo, please run:')
print(' rm -r %s/.repo' % self.manifest.topdir)
print(' rm -r %s' % os.path.join(self.manifest.topdir, '.repo'))
print('and try again.')
def Execute(self, opt, args):
git_require(MIN_GIT_VERSION, fail=True)
def ValidateOptions(self, opt, args):
if opt.reference:
opt.reference = os.path.expanduser(opt.reference)
# Check this here, else manifest will be tagged "not new" and init won't be
# possible anymore without removing the .repo/manifests directory.
if opt.archive and opt.mirror:
print('fatal: --mirror and --archive cannot be used together.',
if opt.mirror:
if opt.archive:
self.OptionParser.error('--mirror and --archive cannot be used '
'together.')
if opt.use_superproject is not None:
self.OptionParser.error('--mirror and --use-superproject cannot be '
'used together.')
if opt.archive and opt.use_superproject is not None:
self.OptionParser.error('--archive and --use-superproject cannot be used '
'together.')
if opt.standalone_manifest and (opt.manifest_branch or
opt.manifest_name != 'default.xml'):
self.OptionParser.error('--manifest-branch and --manifest-name cannot'
' be used with --standalone-manifest.')
if args:
if opt.manifest_url:
self.OptionParser.error(
'--manifest-url option and URL argument both specified: only use '
'one to select the manifest URL.')
opt.manifest_url = args.pop(0)
if args:
self.OptionParser.error('too many arguments to init')
def Execute(self, opt, args):
git_require(MIN_GIT_VERSION_HARD, fail=True)
if not git_require(MIN_GIT_VERSION_SOFT):
print('repo: warning: git-%s+ will soon be required; please upgrade your '
'version of git to maintain support.'
% ('.'.join(str(x) for x in MIN_GIT_VERSION_SOFT),),
file=sys.stderr)
sys.exit(1)
rp = self.manifest.repoProject
# Handle new --repo-url requests.
if opt.repo_url:
remote = rp.GetRemote('origin')
remote.url = opt.repo_url
remote.Save()
# Handle new --repo-rev requests.
if opt.repo_rev:
wrapper = Wrapper()
try:
remote_ref, rev = wrapper.check_repo_rev(
rp.gitdir, opt.repo_rev, repo_verify=opt.repo_verify, quiet=opt.quiet)
except wrapper.CloneFailure:
print('fatal: double check your --repo-rev setting.', file=sys.stderr)
sys.exit(1)
branch = rp.GetBranch('default')
branch.merge = remote_ref
rp.work_git.reset('--hard', rev)
branch.Save()
if opt.worktree:
# Older versions of git supported worktree, but had dangerous gc bugs.
git_require((2, 15, 0), fail=True, msg='git gc worktree corruption')
# Provide a short notice that we're reinitializing an existing checkout.
# Sometimes developers might not realize that they're in one, or that
# repo doesn't do nested checkouts.
existing_checkout = self.manifest.manifestProject.Exists
if not opt.quiet and existing_checkout:
print('repo: reusing existing repo client checkout in', self.manifest.topdir)
self._SyncManifest(opt)
self._LinkManifest(opt.manifest_name)
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
if opt.config_name or self._ShouldConfigureUser():
self._ConfigureUser()
if opt.config_name or self._ShouldConfigureUser(opt, existing_checkout):
self._ConfigureUser(opt)
self._ConfigureColor()
self._ConfigureDepth(opt)
self._DisplayResult()
self._DisplayResult(opt)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2011 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,37 +12,58 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
import os
from command import Command, MirrorSafeCommand
class List(Command, MirrorSafeCommand):
common = True
COMMON = True
helpSummary = "List projects and their associated directories"
helpUsage = """
%prog [-f] [<project>...]
%prog [-f] -r str1 [str2]..."
%prog [-f] -r str1 [str2]...
"""
helpDescription = """
List all projects; pass '.' to list the project for the cwd.
By default, only projects that currently exist in the checkout are shown. If
you want to list all projects (using the specified filter settings), use the
--all option. If you want to show all projects regardless of the manifest
groups, then also pass --groups all.
This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
"""
def _Options(self, p):
p.add_option('-r', '--regex',
dest='regex', action='store_true',
help="Filter the project list based on regex or wildcard matching of strings")
p.add_option('-f', '--fullpath',
dest='fullpath', action='store_true',
help="Display the full work tree path instead of the relative path")
help='filter the project list based on regex or wildcard matching of strings')
p.add_option('-g', '--groups',
dest='groups',
help='filter the project list based on the groups the project is in')
p.add_option('-a', '--all',
action='store_true',
help='show projects regardless of checkout state')
p.add_option('-n', '--name-only',
dest='name_only', action='store_true',
help="Display only the name of the repository")
help='display only the name of the repository')
p.add_option('-p', '--path-only',
dest='path_only', action='store_true',
help="Display only the path of the repository")
help='display only the path of the repository')
p.add_option('-f', '--fullpath',
dest='fullpath', action='store_true',
help='display the full work tree path instead of the relative path')
p.add_option('--relative-to', metavar='PATH',
help='display paths relative to this one (default: top of repo client checkout)')
def ValidateOptions(self, opt, args):
if opt.fullpath and opt.name_only:
self.OptionParser.error('cannot combine -f and -n')
# Resolve any symlinks so the output is stable.
if opt.relative_to:
opt.relative_to = os.path.realpath(opt.relative_to)
def Execute(self, opt, args):
"""List all projects and the associated directories.
@ -56,29 +76,28 @@ This is similar to running: repo forall -c 'echo "$REPO_PATH : $REPO_PROJECT"'.
opt: The options.
args: Positional args. Can be a list of projects to list, or empty.
"""
if opt.fullpath and opt.name_only:
print('error: cannot combine -f and -n', file=sys.stderr)
sys.exit(1)
if not opt.regex:
projects = self.GetProjects(args)
projects = self.GetProjects(args, groups=opt.groups, missing_ok=opt.all,
all_manifests=not opt.this_manifest_only)
else:
projects = self.FindProjects(args)
projects = self.FindProjects(args, all_manifests=not opt.this_manifest_only)
def _getpath(x):
if opt.fullpath:
return x.worktree
return x.relpath
if opt.relative_to:
return os.path.relpath(x.worktree, opt.relative_to)
return x.RelPath(local=opt.this_manifest_only)
lines = []
for project in projects:
if opt.name_only and not opt.path_only:
lines.append("%s" % ( project.name))
lines.append("%s" % (project.name))
elif opt.path_only and not opt.name_only:
lines.append("%s" % (_getpath(project)))
else:
lines.append("%s : %s" % (_getpath(project), project.name))
lines.sort()
print('\n'.join(lines))
if lines:
lines.sort()
print('\n'.join(lines))

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,25 +12,33 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import json
import os
import sys
import optparse
from command import PagedCommand
class Manifest(PagedCommand):
common = False
COMMON = False
helpSummary = "Manifest inspection utility"
helpUsage = """
%prog [-o {-|NAME.xml} [-r]]
%prog [-o {-|NAME.xml}] [-m MANIFEST.xml] [-r]
"""
_helpDescription = """
With the -o option, exports the current manifest for inspection.
The manifest and (if present) local_manifest.xml are combined
The manifest and (if present) local_manifests/ are combined
together to produce a single manifest file. This file can be stored
in a Git repository for use during future 'repo init' invocations.
The -r option can be used to generate a manifest file with project
revisions set to the current commit hash. These are known as
"revision locked manifests", as they don't follow a particular branch.
In this case, the 'upstream' attribute is set to the ref we were on
when the manifest was generated. The 'dest-branch' attribute is set
to indicate the remote ref to push changes to via 'repo upload'.
"""
@property
@ -39,47 +46,87 @@ in a Git repository for use during future 'repo init' invocations.
helptext = self._helpDescription + '\n'
r = os.path.dirname(__file__)
r = os.path.dirname(r)
fd = open(os.path.join(r, 'docs', 'manifest-format.txt'))
for line in fd:
helptext += line
fd.close()
with open(os.path.join(r, 'docs', 'manifest-format.md')) as fd:
for line in fd:
helptext += line
return helptext
def _Options(self, p):
p.add_option('-r', '--revision-as-HEAD',
dest='peg_rev', action='store_true',
help='Save revisions as current HEAD')
help='save revisions as current HEAD')
p.add_option('-m', '--manifest-name',
help='temporary manifest to use for this sync', metavar='NAME.xml')
p.add_option('--suppress-upstream-revision', dest='peg_rev_upstream',
default=True, action='store_false',
help='If in -r mode, do not write the upstream field. '
'Only of use if the branch names for a sha1 manifest are '
'sensitive.')
help='if in -r mode, do not write the upstream field '
'(only of use if the branch names for a sha1 manifest are '
'sensitive)')
p.add_option('--suppress-dest-branch', dest='peg_rev_dest_branch',
default=True, action='store_false',
help='if in -r mode, do not write the dest-branch field '
'(only of use if the branch names for a sha1 manifest are '
'sensitive)')
p.add_option('--json', default=False, action='store_true',
help='output manifest in JSON format (experimental)')
p.add_option('--pretty', default=False, action='store_true',
help='format output for humans to read')
p.add_option('--no-local-manifests', default=False, action='store_true',
dest='ignore_local_manifests', help='ignore local manifests')
p.add_option('-o', '--output-file',
dest='output_file',
default='-',
help='File to save the manifest to',
help='file to save the manifest to. (Filename prefix for multi-tree.)',
metavar='-|NAME.xml')
def _Output(self, opt):
if opt.output_file == '-':
fd = sys.stdout
else:
fd = open(opt.output_file, 'w')
self.manifest.Save(fd,
peg_rev = opt.peg_rev,
peg_rev_upstream = opt.peg_rev_upstream)
fd.close()
if opt.output_file != '-':
print('Saved manifest to %s' % opt.output_file, file=sys.stderr)
# If alternate manifest is specified, override the manifest file that we're using.
if opt.manifest_name:
self.manifest.Override(opt.manifest_name, False)
def Execute(self, opt, args):
for manifest in self.ManifestList(opt):
output_file = opt.output_file
if output_file == '-':
fd = sys.stdout
else:
if manifest.path_prefix:
output_file = f'{opt.output_file}:{manifest.path_prefix.replace("/", "%2f")}'
fd = open(output_file, 'w')
manifest.SetUseLocalManifests(not opt.ignore_local_manifests)
if opt.json:
print('warning: --json is experimental!', file=sys.stderr)
doc = manifest.ToDict(peg_rev=opt.peg_rev,
peg_rev_upstream=opt.peg_rev_upstream,
peg_rev_dest_branch=opt.peg_rev_dest_branch)
json_settings = {
# JSON style guide says Uunicode characters are fully allowed.
'ensure_ascii': False,
# We use 2 space indent to match JSON style guide.
'indent': 2 if opt.pretty else None,
'separators': (',', ': ') if opt.pretty else (',', ':'),
'sort_keys': True,
}
fd.write(json.dumps(doc, **json_settings))
else:
manifest.Save(fd,
peg_rev=opt.peg_rev,
peg_rev_upstream=opt.peg_rev_upstream,
peg_rev_dest_branch=opt.peg_rev_dest_branch)
if output_file != '-':
fd.close()
if manifest.path_prefix:
print(f'Saved {manifest.path_prefix} submanifest to {output_file}',
file=sys.stderr)
else:
print(f'Saved manifest to {output_file}', file=sys.stderr)
def ValidateOptions(self, opt, args):
if args:
self.Usage()
if opt.output_file is not None:
self._Output(opt)
return
print('error: no operation to perform', file=sys.stderr)
print('error: see repo help manifest', file=sys.stderr)
sys.exit(1)
def Execute(self, opt, args):
self._Output(opt)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,13 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import optparse
from color import Coloring
from command import PagedCommand
class Overview(PagedCommand):
common = True
COMMON = True
helpSummary = "Display overview of unmerged project branches"
helpUsage = """
%prog [--current-branch] [<project>...]
@ -28,19 +28,26 @@ class Overview(PagedCommand):
The '%prog' command is used to display an overview of the projects branches,
and list any local commits that have not yet been merged into the project.
The -b/--current-branch option can be used to restrict the output to only
The -c/--current-branch option can be used to restrict the output to only
branches currently checked out in each project. By default, all branches
are displayed.
"""
def _Options(self, p):
p.add_option('-b', '--current-branch',
p.add_option('-c', '--current-branch',
dest="current_branch", action="store_true",
help="Consider only checked out branches")
help="consider only checked out branches")
p.add_option('--no-current-branch',
dest='current_branch', action='store_false',
help='consider all local branches')
# Turn this into a warning & remove this someday.
p.add_option('-b',
dest='current_branch', action='store_true',
help=optparse.SUPPRESS_HELP)
def Execute(self, opt, args):
all_branches = []
for project in self.GetProjects(args):
for project in self.GetProjects(args, all_manifests=not opt.this_manifest_only):
br = [project.GetUploadableBranch(x)
for x in project.GetBranches()]
br = [x for x in br if x]
@ -69,7 +76,7 @@ are displayed.
if project != branch.project:
project = branch.project
out.nl()
out.project('project %s/' % project.relpath)
out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
out.nl()
commits = branch.commits

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,21 +12,38 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import itertools
from color import Coloring
from command import PagedCommand
from command import DEFAULT_LOCAL_JOBS, PagedCommand
class Prune(PagedCommand):
common = True
COMMON = True
helpSummary = "Prune (delete) already merged topics"
helpUsage = """
%prog [<project>...]
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def _ExecuteOne(self, project):
"""Process one project."""
return project.PruneHeads()
def Execute(self, opt, args):
all_branches = []
for project in self.GetProjects(args):
all_branches.extend(project.PruneHeads())
projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
# NB: Should be able to refactor this module to display summary as results
# come back from children.
def _ProcessResults(_pool, _output, results):
return list(itertools.chain.from_iterable(results))
all_branches = self.ExecuteInParallel(
opt.jobs,
self._ExecuteOne,
projects,
callback=_ProcessResults,
ordered=True)
if not all_branches:
return
@ -47,14 +63,19 @@ class Prune(PagedCommand):
if project != branch.project:
project = branch.project
out.nl()
out.project('project %s/' % project.relpath)
out.project('project %s/' % project.RelPath(local=opt.this_manifest_only))
out.nl()
commits = branch.commits
date = branch.date
print('%s %-33s (%2d commit%s, %s)' % (
print('%s %-33s ' % (
branch.name == project.CurrentBranch and '*' or ' ',
branch.name,
branch.name), end='')
if not branch.base_exists:
print('(ignoring: tracking branch is gone: %s)' % (branch.base,))
else:
commits = branch.commits
date = branch.date
print('(%2d commit%s, %s)' % (
len(commits),
len(commits) != 1 and 's' or ' ',
date))

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2010 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,14 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from color import Coloring
from command import Command
from git_command import GitCommand
class RebaseColoring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'rebase')
self.project = self.printer('project', attr='bold')
self.fail = self.printer('fail', fg='red')
class Rebase(Command):
common = True
COMMON = True
helpSummary = "Rebase local branches on upstream branch"
helpUsage = """
%prog {[<project>...] | -i <project>...}
@ -32,31 +39,37 @@ branch but need to incorporate new upstream changes "underneath" them.
"""
def _Options(self, p):
p.add_option('-i', '--interactive',
dest="interactive", action="store_true",
help="interactive rebase (single project only)")
g = p.get_option_group('--quiet')
g.add_option('-i', '--interactive',
dest="interactive", action="store_true",
help="interactive rebase (single project only)")
p.add_option('--fail-fast',
dest='fail_fast', action='store_true',
help='stop rebasing after first error is hit')
p.add_option('-f', '--force-rebase',
dest='force_rebase', action='store_true',
help='Pass --force-rebase to git rebase')
help='pass --force-rebase to git rebase')
p.add_option('--no-ff',
dest='no_ff', action='store_true',
help='Pass --no-ff to git rebase')
p.add_option('-q', '--quiet',
dest='quiet', action='store_true',
help='Pass --quiet to git rebase')
dest='ff', default=True, action='store_false',
help='pass --no-ff to git rebase')
p.add_option('--autosquash',
dest='autosquash', action='store_true',
help='Pass --autosquash to git rebase')
help='pass --autosquash to git rebase')
p.add_option('--whitespace',
dest='whitespace', action='store', metavar='WS',
help='Pass --whitespace to git rebase')
help='pass --whitespace to git rebase')
p.add_option('--auto-stash',
dest='auto_stash', action='store_true',
help='Stash local modifications before starting')
help='stash local modifications before starting')
p.add_option('-m', '--onto-manifest',
dest='onto_manifest', action='store_true',
help='rebase onto the manifest version instead of upstream '
'HEAD (this helps to make sure the local tree stays '
'consistent if you previously synced to a manifest)')
def Execute(self, opt, args):
all_projects = self.GetProjects(args)
all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
one_project = len(all_projects) == 1
if opt.interactive and not one_project:
@ -64,16 +77,40 @@ branch but need to incorporate new upstream changes "underneath" them.
file=sys.stderr)
if len(args) == 1:
print('note: project %s is mapped to more than one path' % (args[0],),
file=sys.stderr)
return -1
file=sys.stderr)
return 1
# Setup the common git rebase args that we use for all projects.
common_args = ['rebase']
if opt.whitespace:
common_args.append('--whitespace=%s' % opt.whitespace)
if opt.quiet:
common_args.append('--quiet')
if opt.force_rebase:
common_args.append('--force-rebase')
if not opt.ff:
common_args.append('--no-ff')
if opt.autosquash:
common_args.append('--autosquash')
if opt.interactive:
common_args.append('-i')
config = self.manifest.manifestProject.config
out = RebaseColoring(config)
out.redirect(sys.stdout)
_RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
ret = 0
for project in all_projects:
if ret and opt.fail_fast:
break
cb = project.CurrentBranch
if not cb:
if one_project:
print("error: project %s has a detached HEAD" % project.relpath,
print("error: project %s has a detached HEAD" % _RelPath(project),
file=sys.stderr)
return -1
return 1
# ignore branches with detatched HEADs
continue
@ -81,35 +118,22 @@ branch but need to incorporate new upstream changes "underneath" them.
if not upbranch.LocalMerge:
if one_project:
print("error: project %s does not track any remote branches"
% project.relpath, file=sys.stderr)
return -1
% _RelPath(project), file=sys.stderr)
return 1
# ignore branches without remotes
continue
args = ["rebase"]
if opt.whitespace:
args.append('--whitespace=%s' % opt.whitespace)
if opt.quiet:
args.append('--quiet')
if opt.force_rebase:
args.append('--force-rebase')
if opt.no_ff:
args.append('--no-ff')
if opt.autosquash:
args.append('--autosquash')
if opt.interactive:
args.append("-i")
args = common_args[:]
if opt.onto_manifest:
args.append('--onto')
args.append(project.revisionExpr)
args.append(upbranch.LocalMerge)
print('# %s: rebasing %s -> %s'
% (project.relpath, cb, upbranch.LocalMerge), file=sys.stderr)
out.project('project %s: rebasing %s -> %s',
_RelPath(project), cb, upbranch.LocalMerge)
out.nl()
out.flush()
needs_stash = False
if opt.auto_stash:
@ -121,13 +145,21 @@ branch but need to incorporate new upstream changes "underneath" them.
stash_args = ["stash"]
if GitCommand(project, stash_args).Wait() != 0:
return -1
ret += 1
continue
if GitCommand(project, args).Wait() != 0:
return -1
ret += 1
continue
if needs_stash:
stash_args.append('pop')
stash_args.append('--quiet')
if GitCommand(project, stash_args).Wait() != 0:
return -1
ret += 1
if ret:
out.fail('%i projects had errors', ret)
out.nl()
return ret

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from optparse import SUPPRESS_HELP
import sys
@ -21,8 +19,9 @@ from command import Command, MirrorSafeCommand
from subcmds.sync import _PostRepoUpgrade
from subcmds.sync import _PostRepoFetch
class Selfupdate(Command, MirrorSafeCommand):
common = False
COMMON = False
helpSummary = "Update repo to the latest version"
helpUsage = """
%prog
@ -38,7 +37,7 @@ need to be performed by an end-user.
def _Options(self, p):
g = p.add_option_group('repo Version options')
g.add_option('--no-repo-verify',
dest='no_repo_verify', action='store_true',
dest='repo_verify', default=True, action='store_false',
help='do not verify repo source code')
g.add_option('--repo-upgraded',
dest='repo_upgraded', action='store_true',
@ -52,11 +51,11 @@ need to be performed by an end-user.
_PostRepoUpgrade(self.manifest)
else:
if not rp.Sync_NetworkHalf():
if not rp.Sync_NetworkHalf().success:
print("error: can't update repo", file=sys.stderr)
sys.exit(1)
rp.bare_git.gc('--auto')
_PostRepoFetch(rp,
no_repo_verify = opt.no_repo_verify,
verbose = True)
repo_verify=opt.repo_verify,
verbose=True)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2010 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -15,8 +14,9 @@
from subcmds.sync import Sync
class Smartsync(Sync):
common = True
COMMON = True
helpSummary = "Update working tree to the latest known good revision"
helpUsage = """
%prog [<project>...]

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,13 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import sys
from color import Coloring
from command import InteractiveCommand
from git_command import GitCommand
class _ProjectList(Coloring):
def __init__(self, gc):
Coloring.__init__(self, gc, 'interactive')
@ -27,8 +26,9 @@ class _ProjectList(Coloring):
self.header = self.printer('header', attr='bold')
self.help = self.printer('help', fg='red', attr='bold')
class Stage(InteractiveCommand):
common = True
COMMON = True
helpSummary = "Stage file(s) for commit"
helpUsage = """
%prog -i [<project>...]
@ -38,7 +38,8 @@ The '%prog' command stages files to prepare the next commit.
"""
def _Options(self, p):
p.add_option('-i', '--interactive',
g = p.get_option_group('--quiet')
g.add_option('-i', '--interactive',
dest='interactive', action='store_true',
help='use interactive staging')
@ -49,7 +50,9 @@ The '%prog' command stages files to prepare the next commit.
self.Usage()
def _Interactive(self, opt, args):
all_projects = [p for p in self.GetProjects(args) if p.IsDirty()]
all_projects = [
p for p in self.GetProjects(args, all_manifests=not opt.this_manifest_only)
if p.IsDirty()]
if not all_projects:
print('no projects have uncommitted modifications', file=sys.stderr)
return
@ -60,8 +63,9 @@ The '%prog' command stages files to prepare the next commit.
out.nl()
for i in range(len(all_projects)):
p = all_projects[i]
out.write('%3d: %s', i + 1, p.relpath + '/')
project = all_projects[i]
out.write('%3d: %s', i + 1,
project.RelPath(local=opt.this_manifest_only) + '/')
out.nl()
out.nl()
@ -71,6 +75,7 @@ The '%prog' command stages files to prepare the next commit.
out.nl()
out.prompt('project> ')
out.flush()
try:
a = sys.stdin.readline()
except KeyboardInterrupt:
@ -98,12 +103,15 @@ The '%prog' command stages files to prepare the next commit.
_AddI(all_projects[a_index - 1])
continue
projects = [p for p in all_projects if a in [p.name, p.relpath]]
projects = [
p for p in all_projects
if a in [p.name, p.RelPath(local=opt.this_manifest_only)]]
if len(projects) == 1:
_AddI(projects[0])
continue
print('Bye.')
def _AddI(project):
p = GitCommand(project, ['add', '--interactive'], bare=False)
p.Wait()

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,15 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import functools
import os
import sys
from command import Command
from git_config import IsId
from command import Command, DEFAULT_LOCAL_JOBS
from git_config import IsImmutable
from git_command import git
import gitc_utils
from progress import Progress
from project import SyncBuffer
class Start(Command):
common = True
COMMON = True
helpSummary = "Start a new branch for development"
helpUsage = """
%prog <newbranchname> [--all | <project>...]
@ -30,44 +34,110 @@ class Start(Command):
'%prog' begins a new branch of development, starting from the
revision specified in the manifest.
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def _Options(self, p):
p.add_option('--all',
dest='all', action='store_true',
help='begin branch in all projects')
p.add_option('-r', '--rev', '--revision', dest='revision',
help='point branch at this revision instead of upstream')
p.add_option('--head', '--HEAD',
dest='revision', action='store_const', const='HEAD',
help='abbreviation for --rev HEAD')
def Execute(self, opt, args):
def ValidateOptions(self, opt, args):
if not args:
self.Usage()
nb = args[0]
if not git.check_ref_format('heads/%s' % nb):
print("error: '%s' is not a valid name" % nb, file=sys.stderr)
sys.exit(1)
self.OptionParser.error("'%s' is not a valid name" % nb)
def _ExecuteOne(self, revision, nb, project):
"""Start one project."""
# If the current revision is immutable, such as a SHA1, a tag or
# a change, then we can't push back to it. Substitute with
# dest_branch, if defined; or with manifest default revision instead.
branch_merge = ''
if IsImmutable(project.revisionExpr):
if project.dest_branch:
branch_merge = project.dest_branch
else:
branch_merge = self.manifest.default.revisionExpr
try:
ret = project.StartBranch(
nb, branch_merge=branch_merge, revision=revision)
except Exception as e:
print('error: unable to checkout %s: %s' % (project.name, e), file=sys.stderr)
ret = False
return (ret, project)
def Execute(self, opt, args):
nb = args[0]
err = []
projects = []
if not opt.all:
projects = args[1:]
if len(projects) < 1:
print("error: at least one project must be specified", file=sys.stderr)
sys.exit(1)
projects = ['.'] # start it in the local project by default
all_projects = self.GetProjects(projects)
all_projects = self.GetProjects(projects,
missing_ok=bool(self.gitc_manifest),
all_manifests=not opt.this_manifest_only)
pm = Progress('Starting %s' % nb, len(all_projects))
for project in all_projects:
pm.update()
# If the current revision is a specific SHA1 then we can't push back
# to it so substitute the manifest default revision instead.
if IsId(project.revisionExpr):
project.revisionExpr = self.manifest.default.revisionExpr
if not project.StartBranch(nb):
err.append(project)
pm.end()
# This must happen after we find all_projects, since GetProjects may need
# the local directory, which will disappear once we save the GITC manifest.
if self.gitc_manifest:
gitc_projects = self.GetProjects(projects, manifest=self.gitc_manifest,
missing_ok=True)
for project in gitc_projects:
if project.old_revision:
project.already_synced = True
else:
project.already_synced = False
project.old_revision = project.revisionExpr
project.revisionExpr = None
# Save the GITC manifest.
gitc_utils.save_manifest(self.gitc_manifest)
# Make sure we have a valid CWD
if not os.path.exists(os.getcwd()):
os.chdir(self.manifest.topdir)
pm = Progress('Syncing %s' % nb, len(all_projects), quiet=opt.quiet)
for project in all_projects:
gitc_project = self.gitc_manifest.paths[project.relpath]
# Sync projects that have not been opened.
if not gitc_project.already_synced:
proj_localdir = os.path.join(self.gitc_manifest.gitc_client_dir,
project.relpath)
project.worktree = proj_localdir
if not os.path.exists(proj_localdir):
os.makedirs(proj_localdir)
project.Sync_NetworkHalf()
sync_buf = SyncBuffer(self.manifest.manifestProject.config)
project.Sync_LocalHalf(sync_buf)
project.revisionId = gitc_project.old_revision
pm.update()
pm.end()
def _ProcessResults(_pool, pm, results):
for (result, project) in results:
if not result:
err.append(project)
pm.update()
self.ExecuteInParallel(
opt.jobs,
functools.partial(self._ExecuteOne, opt.revision, nb),
all_projects,
callback=_ProcessResults,
output=Progress('Starting %s' % (nb,), len(all_projects), quiet=opt.quiet))
if err:
for p in err:
print("error: %s/: cannot start %s" % (p.relpath, nb),
print("error: %s/: cannot start %s" % (p.RelPath(local=opt.this_manifest_only), nb),
file=sys.stderr)
sys.exit(1)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,29 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from command import PagedCommand
try:
import threading as _threading
except ImportError:
import dummy_threading as _threading
import functools
import glob
from pyversion import is_python3
if is_python3():
import io
else:
import StringIO as io
import itertools
import io
import os
import sys
from command import DEFAULT_LOCAL_JOBS, PagedCommand
from color import Coloring
import platform_utils
class Status(PagedCommand):
common = True
COMMON = True
helpSummary = "Show the working tree status"
helpUsage = """
%prog [<project>...]
@ -56,8 +45,7 @@ includes deeper items. For example, if dir/subdir/proj1 and
dir/subdir/proj2 are repo projects, dir/subdir/proj3 will be shown
if it is not known to repo.
Status Display
--------------
# Status Display
The status display is organized into three columns of information,
for example if the file 'subcmds/status.py' is modified in the
@ -88,91 +76,79 @@ the following meanings:
d: deleted ( in index, not in work tree )
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def _Options(self, p):
p.add_option('-j', '--jobs',
dest='jobs', action='store', type='int', default=2,
help="number of projects to check simultaneously")
p.add_option('-o', '--orphans',
dest='orphans', action='store_true',
help="include objects in working directory outside of repo projects")
def _StatusHelper(self, project, clean_counter, sem, output):
def _StatusHelper(self, quiet, local, project):
"""Obtains the status for a specific project.
Obtains the status for a project, redirecting the output to
the specified object. It will release the semaphore
when done.
the specified object.
Args:
quiet: Where to output the status.
local: a boolean, if True, the path is relative to the local
(sub)manifest. If false, the path is relative to the
outermost manifest.
project: Project to get status of.
clean_counter: Counter for clean projects.
sem: Semaphore, will call release() when complete.
output: Where to output the status.
Returns:
The status of the project.
"""
try:
state = project.PrintWorkTreeStatus(output)
if state == 'CLEAN':
clean_counter.next()
finally:
sem.release()
buf = io.StringIO()
ret = project.PrintWorkTreeStatus(quiet=quiet, output_redir=buf,
local=local)
return (ret, buf.getvalue())
def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring):
"""find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'"""
status_header = ' --\t'
for item in dirs:
if not os.path.isdir(item):
outstring.write(''.join([status_header, item]))
if not platform_utils.isdir(item):
outstring.append(''.join([status_header, item]))
continue
if item in proj_dirs:
continue
if item in proj_dirs_parents:
self._FindOrphans(glob.glob('%s/.*' % item) + \
glob.glob('%s/*' % item), \
proj_dirs, proj_dirs_parents, outstring)
self._FindOrphans(glob.glob('%s/.*' % item) +
glob.glob('%s/*' % item),
proj_dirs, proj_dirs_parents, outstring)
continue
outstring.write(''.join([status_header, item, '/']))
outstring.append(''.join([status_header, item, '/']))
def Execute(self, opt, args):
all_projects = self.GetProjects(args)
counter = itertools.count()
all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
if opt.jobs == 1:
for project in all_projects:
state = project.PrintWorkTreeStatus()
def _ProcessResults(_pool, _output, results):
ret = 0
for (state, output) in results:
if output:
print(output, end='')
if state == 'CLEAN':
counter.next()
else:
sem = _threading.Semaphore(opt.jobs)
threads_and_output = []
for project in all_projects:
sem.acquire()
ret += 1
return ret
class BufList(io.StringIO):
def dump(self, ostream):
for entry in self.buflist:
ostream.write(entry)
counter = self.ExecuteInParallel(
opt.jobs,
functools.partial(self._StatusHelper, opt.quiet, opt.this_manifest_only),
all_projects,
callback=_ProcessResults,
ordered=True)
output = BufList()
t = _threading.Thread(target=self._StatusHelper,
args=(project, counter, sem, output))
threads_and_output.append((t, output))
t.daemon = True
t.start()
for (t, output) in threads_and_output:
t.join()
output.dump(sys.stdout)
output.close()
if len(all_projects) == counter.next():
if not opt.quiet and len(all_projects) == counter:
print('nothing to commit (working directory clean)')
if opt.orphans:
proj_dirs = set()
proj_dirs_parents = set()
for project in self.GetProjects(None, missing_ok=True):
proj_dirs.add(project.relpath)
(head, _tail) = os.path.split(project.relpath)
for project in self.GetProjects(None, missing_ok=True, all_manifests=not opt.this_manifest_only):
relpath = project.RelPath(local=opt.this_manifest_only)
proj_dirs.add(relpath)
(head, _tail) = os.path.split(relpath)
while head != "":
proj_dirs_parents.add(head)
(head, _tail) = os.path.split(head)
@ -181,30 +157,28 @@ the following meanings:
class StatusColoring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, 'status')
self.project = self.printer('header', attr = 'bold')
self.untracked = self.printer('untracked', fg = 'red')
self.project = self.printer('header', attr='bold')
self.untracked = self.printer('untracked', fg='red')
orig_path = os.getcwd()
try:
os.chdir(self.manifest.topdir)
outstring = io.StringIO()
self._FindOrphans(glob.glob('.*') + \
glob.glob('*'), \
proj_dirs, proj_dirs_parents, outstring)
outstring = []
self._FindOrphans(glob.glob('.*') +
glob.glob('*'),
proj_dirs, proj_dirs_parents, outstring)
if outstring.buflist:
output = StatusColoring(self.manifest.globalConfig)
if outstring:
output = StatusColoring(self.client.globalConfig)
output.project('Objects not within a project (orphans)')
output.nl()
for entry in outstring.buflist:
for entry in outstring:
output.untracked(entry)
output.nl()
else:
print('No orphan files or directories')
outstring.close()
finally:
# Restore CWD.
os.chdir(orig_path)

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,25 +12,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import copy
import functools
import optparse
import re
import sys
from command import InteractiveCommand
from command import DEFAULT_LOCAL_JOBS, InteractiveCommand
from editor import Editor
from error import HookError, UploadError
from error import UploadError
from git_command import GitCommand
from project import RepoHook
from git_refs import R_HEADS
from hooks import RepoHook
from pyversion import is_python3
if not is_python3():
# pylint:disable=W0622
input = raw_input
# pylint:enable=W0622
UNUSUAL_COMMIT_THRESHOLD = 5
def _ConfirmManyUploads(multiple_branches=False):
if multiple_branches:
print('ATTENTION: One or more branches has an unusually high number '
@ -43,19 +40,22 @@ def _ConfirmManyUploads(multiple_branches=False):
answer = input("If you are sure you intend to do this, type 'yes': ").strip()
return answer == "yes"
def _die(fmt, *args):
msg = fmt % args
print('error: %s' % msg, file=sys.stderr)
sys.exit(1)
def _SplitEmails(values):
result = []
for value in values:
result.extend([s.strip() for s in value.split(',')])
return result
class Upload(InteractiveCommand):
common = True
COMMON = True
helpSummary = "Upload changes for code review"
helpUsage = """
%prog [--re --cc] [<project>]...
@ -78,8 +78,14 @@ added to the respective list of users, and emails are sent to any
new users. Users passed as --reviewers must already be registered
with the code review system, or the upload will fail.
Configuration
-------------
While most normal Gerrit options have dedicated command line options,
direct access to the Gerit options is available via --push-options.
This is useful when Gerrit has newer functionality that %prog doesn't
yet support, or doesn't have plans to support. See the Push Options
documentation for more details:
https://gerrit-review.googlesource.com/Documentation/user-upload.html#push_options
# Configuration
review.URL.autoupload:
@ -126,59 +132,98 @@ is set to "true" then repo will assume you always want the equivalent
of the -t option to the repo command. If unset or set to "false" then
repo will make use of only the command line option.
References
----------
review.URL.uploadhashtags:
Gerrit Code Review: http://code.google.com/p/gerrit/
To add hashtags whenever uploading a commit, you can set a per-project
or global Git option to do so. The value of review.URL.uploadhashtags
will be used as comma delimited hashtags like the --hashtag option.
review.URL.uploadlabels:
To add labels whenever uploading a commit, you can set a per-project
or global Git option to do so. The value of review.URL.uploadlabels
will be used as comma delimited labels like the --label option.
review.URL.uploadnotify:
Control e-mail notifications when uploading.
https://gerrit-review.googlesource.com/Documentation/user-upload.html#notify
# References
Gerrit Code Review: https://www.gerritcodereview.com/
"""
PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
def _Options(self, p):
p.add_option('-t',
dest='auto_topic', action='store_true',
help='Send local branch name to Gerrit Code Review')
help='send local branch name to Gerrit Code Review')
p.add_option('--hashtag', '--ht',
dest='hashtags', action='append', default=[],
help='add hashtags (comma delimited) to the review')
p.add_option('--hashtag-branch', '--htb',
action='store_true',
help='add local branch name as a hashtag')
p.add_option('-l', '--label',
dest='labels', action='append', default=[],
help='add a label when uploading')
p.add_option('--re', '--reviewers',
type='string', action='append', dest='reviewers',
help='Request reviews from these people.')
type='string', action='append', dest='reviewers',
help='request reviews from these people')
p.add_option('--cc',
type='string', action='append', dest='cc',
help='Also send email to these email addresses.')
p.add_option('--br',
type='string', action='store', dest='branch',
help='Branch to upload.')
p.add_option('--cbr', '--current-branch',
type='string', action='append', dest='cc',
help='also send email to these email addresses')
p.add_option('--br', '--branch',
type='string', action='store', dest='branch',
help='(local) branch to upload')
p.add_option('-c', '--current-branch',
dest='current_branch', action='store_true',
help='Upload current git branch.')
p.add_option('-d', '--draft',
action='store_true', dest='draft', default=False,
help='If specified, upload as a draft.')
help='upload current git branch')
p.add_option('--no-current-branch',
dest='current_branch', action='store_false',
help='upload all git branches')
# Turn this into a warning & remove this someday.
p.add_option('--cbr',
dest='current_branch', action='store_true',
help=optparse.SUPPRESS_HELP)
p.add_option('--ne', '--no-emails',
action='store_false', dest='notify', default=True,
help='do not send e-mails on upload')
p.add_option('-p', '--private',
action='store_true', dest='private', default=False,
help='upload as a private change (deprecated; use --wip)')
p.add_option('-w', '--wip',
action='store_true', dest='wip', default=False,
help='upload as a work-in-progress change')
p.add_option('-r', '--ready',
action='store_true', default=False,
help='mark change as ready (clears work-in-progress setting)')
p.add_option('-o', '--push-option',
type='string', action='append', dest='push_options',
default=[],
help='additional push options to transmit')
p.add_option('-D', '--destination', '--dest',
type='string', action='store', dest='dest_branch',
metavar='BRANCH',
help='Submit for review on this target branch.')
# Options relating to upload hook. Note that verify and no-verify are NOT
# opposites of each other, which is why they store to different locations.
# We are using them to match 'git commit' syntax.
#
# Combinations:
# - no-verify=False, verify=False (DEFAULT):
# If stdout is a tty, can prompt about running upload hooks if needed.
# If user denies running hooks, the upload is cancelled. If stdout is
# not a tty and we would need to prompt about upload hooks, upload is
# cancelled.
# - no-verify=False, verify=True:
# Always run upload hooks with no prompt.
# - no-verify=True, verify=False:
# Never run upload hooks, but upload anyway (AKA bypass hooks).
# - no-verify=True, verify=True:
# Invalid
p.add_option('--no-verify',
dest='bypass_hooks', action='store_true',
help='Do not run the upload hook.')
p.add_option('--verify',
dest='allow_all_hooks', action='store_true',
help='Run the upload hook without prompting.')
help='submit for review on this target branch')
p.add_option('-n', '--dry-run',
dest='dryrun', default=False, action='store_true',
help='do everything except actually upload the CL')
p.add_option('-y', '--yes',
default=False, action='store_true',
help='answer yes to all safe prompts')
p.add_option('--ignore-untracked-files',
action='store_true', default=False,
help='ignore untracked files in the working copy')
p.add_option('--no-ignore-untracked-files',
dest='ignore_untracked_files', action='store_false',
help='always ask about untracked files in the working copy')
p.add_option('--no-cert-checks',
dest='validate_certs', action='store_false', default=True,
help='disable verifying ssl certs (unsafe)')
RepoHook.AddOptionGroup(p, 'pre-upload')
def _SingleBranch(self, opt, branch, people):
project = branch.project
@ -196,20 +241,28 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
commit_list = branch.commits
destination = opt.dest_branch or project.dest_branch or project.revisionExpr
print('Upload project %s/ to remote branch %s:' % (project.relpath, destination))
print('Upload project %s/ to remote branch %s%s:' %
(project.RelPath(local=opt.this_manifest_only), destination,
' (private)' if opt.private else ''))
print(' branch %s (%2d commit%s, %s):' % (
name,
len(commit_list),
len(commit_list) != 1 and 's' or '',
date))
name,
len(commit_list),
len(commit_list) != 1 and 's' or '',
date))
for commit in commit_list:
print(' %s' % commit)
sys.stdout.write('to %s (y/N)? ' % remote.review)
answer = sys.stdin.readline().strip().lower()
answer = answer in ('y', 'yes', '1', 'true', 't')
print('to %s (y/N)? ' % remote.review, end='')
# TODO: When we require Python 3, use flush=True w/print above.
sys.stdout.flush()
if opt.yes:
print('<--yes>')
answer = True
else:
answer = sys.stdin.readline().strip().lower()
answer = answer in ('y', 'yes', '1', 'true', 't')
if answer:
if not opt.yes and answer:
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
answer = _ConfirmManyUploads()
@ -226,7 +279,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
script.append('# Uncomment the branches to upload:')
for project, avail in pending:
script.append('#')
script.append('# project %s/:' % project.relpath)
script.append('# project %s/:' % project.RelPath(local=opt.this_manifest_only))
b = {}
for branch in avail:
@ -249,15 +302,10 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
script.append('# %s' % commit)
b[name] = branch
projects[project.relpath] = project
projects[project.RelPath(local=opt.this_manifest_only)] = project
branches[project.name] = b
script.append('')
script = [ x.encode('utf-8')
if issubclass(type(x), unicode)
else x
for x in script ]
script = Editor.EditString("\n".join(script)).split("\n")
project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
@ -282,19 +330,20 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
_die('project for branch %s not in script', name)
branch = branches[project.name].get(name)
if not branch:
_die('branch %s not in %s', name, project.relpath)
_die('branch %s not in %s', name, project.RelPath(local=opt.this_manifest_only))
todo.append(branch)
if not todo:
_die("nothing uncommented for upload")
many_commits = False
for branch in todo:
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
many_commits = True
break
if many_commits:
if not _ConfirmManyUploads(multiple_branches=True):
_die("upload aborted by user")
if not opt.yes:
many_commits = False
for branch in todo:
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
many_commits = True
break
if many_commits:
if not _ConfirmManyUploads(multiple_branches=True):
_die("upload aborted by user")
self._UploadAndReport(opt, todo, people)
@ -309,12 +358,12 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
key = 'review.%s.autoreviewer' % project.GetBranch(name).remote.review
raw_list = project.config.GetString(key)
if not raw_list is None:
if raw_list is not None:
people[0].extend([entry.strip() for entry in raw_list.split(',')])
key = 'review.%s.autocopy' % project.GetBranch(name).remote.review
raw_list = project.config.GetString(key)
if not raw_list is None and len(people[0]) > 0:
if raw_list is not None and len(people[0]) > 0:
people[1].extend([entry.strip() for entry in raw_list.split(',')])
def _FindGerritChange(self, branch):
@ -337,14 +386,29 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
self._AppendAutoList(branch, people)
# Check if there are local changes that may have been forgotten
if branch.project.HasChanges():
changes = branch.project.UncommitedFiles()
if opt.ignore_untracked_files:
untracked = set(branch.project.UntrackedFiles())
changes = [x for x in changes if x not in untracked]
if changes:
key = 'review.%s.autoupload' % branch.project.remote.review
answer = branch.project.config.GetBoolean(key)
# if they want to auto upload, let's not ask because it could be automated
if answer is None:
sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/N) ')
a = sys.stdin.readline().strip().lower()
print()
print('Uncommitted changes in %s (did you forget to amend?):'
% branch.project.name)
print('\n'.join(changes))
print('Continue uploading? (y/N) ', end='')
# TODO: When we require Python 3, use flush=True w/print above.
sys.stdout.flush()
if opt.yes:
print('<--yes>')
a = 'yes'
else:
a = sys.stdin.readline().strip().lower()
if a not in ('y', 'yes', 't', 'true', 'on'):
print("skipping upload", file=sys.stderr)
branch.uploaded = False
@ -356,12 +420,45 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
key = 'review.%s.uploadtopic' % branch.project.remote.review
opt.auto_topic = branch.project.config.GetBoolean(key)
def _ExpandCommaList(value):
"""Split |value| up into comma delimited entries."""
if not value:
return
for ret in value.split(','):
ret = ret.strip()
if ret:
yield ret
# Check if hashtags should be included.
key = 'review.%s.uploadhashtags' % branch.project.remote.review
hashtags = set(_ExpandCommaList(branch.project.config.GetString(key)))
for tag in opt.hashtags:
hashtags.update(_ExpandCommaList(tag))
if opt.hashtag_branch:
hashtags.add(branch.name)
# Check if labels should be included.
key = 'review.%s.uploadlabels' % branch.project.remote.review
labels = set(_ExpandCommaList(branch.project.config.GetString(key)))
for label in opt.labels:
labels.update(_ExpandCommaList(label))
# Handle e-mail notifications.
if opt.notify is False:
notify = 'NONE'
else:
key = 'review.%s.uploadnotify' % branch.project.remote.review
notify = branch.project.config.GetString(key)
destination = opt.dest_branch or branch.project.dest_branch
# Make sure our local branch is not setup to track a different remote branch
merge_branch = self._GetMergeBranch(branch.project)
if destination:
full_dest = 'refs/heads/%s' % destination
full_dest = destination
if not full_dest.startswith(R_HEADS):
full_dest = R_HEADS + full_dest
if not opt.dest_branch and merge_branch and merge_branch != full_dest:
print('merge branch %s does not match destination branch %s'
% (merge_branch, full_dest))
@ -371,7 +468,19 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
branch.uploaded = False
continue
branch.UploadForReview(people, auto_topic=opt.auto_topic, draft=opt.draft, dest_branch=destination)
branch.UploadForReview(people,
dryrun=opt.dryrun,
auto_topic=opt.auto_topic,
hashtags=hashtags,
labels=labels,
private=opt.private,
notify=notify,
wip=opt.wip,
ready=opt.ready,
dest_branch=destination,
validate_certs=opt.validate_certs,
push_options=opt.push_options)
branch.uploaded = True
except UploadError as e:
branch.error = e
@ -389,18 +498,18 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
else:
fmt = '\n (%s)'
print(('[FAILED] %-15s %-15s' + fmt) % (
branch.project.relpath + '/', \
branch.name, \
str(branch.error)),
file=sys.stderr)
branch.project.RelPath(local=opt.this_manifest_only) + '/',
branch.name,
str(branch.error)),
file=sys.stderr)
print()
for branch in todo:
if branch.uploaded:
print('[OK ] %-15s %s' % (
branch.project.relpath + '/',
branch.name),
file=sys.stderr)
branch.project.RelPath(local=opt.this_manifest_only) + '/',
branch.name),
file=sys.stderr)
if have_errors:
sys.exit(1)
@ -408,67 +517,84 @@ Gerrit Code Review: http://code.google.com/p/gerrit/
def _GetMergeBranch(self, project):
p = GitCommand(project,
['rev-parse', '--abbrev-ref', 'HEAD'],
capture_stdout = True,
capture_stderr = True)
capture_stdout=True,
capture_stderr=True)
p.Wait()
local_branch = p.stdout.strip()
p = GitCommand(project,
['config', '--get', 'branch.%s.merge' % local_branch],
capture_stdout = True,
capture_stderr = True)
capture_stdout=True,
capture_stderr=True)
p.Wait()
merge_branch = p.stdout.strip()
return merge_branch
@staticmethod
def _GatherOne(opt, project):
"""Figure out the upload status for |project|."""
if opt.current_branch:
cbr = project.CurrentBranch
up_branch = project.GetUploadableBranch(cbr)
avail = [up_branch] if up_branch else None
else:
avail = project.GetUploadableBranches(opt.branch)
return (project, avail)
def Execute(self, opt, args):
project_list = self.GetProjects(args)
pending = []
reviewers = []
cc = []
branch = None
projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
if opt.branch:
branch = opt.branch
for project in project_list:
if opt.current_branch:
cbr = project.CurrentBranch
up_branch = project.GetUploadableBranch(cbr)
if up_branch:
avail = [up_branch]
else:
avail = None
print('ERROR: Current branch (%s) not uploadable. '
'You may be able to type '
'"git branch --set-upstream-to m/master" to fix '
'your branch.' % str(cbr),
def _ProcessResults(_pool, _out, results):
pending = []
for result in results:
project, avail = result
if avail is None:
print('repo: error: %s: Unable to upload branch "%s". '
'You might be able to fix the branch by running:\n'
' git branch --set-upstream-to m/%s' %
(project.RelPath(local=opt.this_manifest_only), project.CurrentBranch,
project.manifest.branch),
file=sys.stderr)
else:
avail = project.GetUploadableBranches(branch)
if avail:
pending.append((project, avail))
elif avail:
pending.append(result)
return pending
if pending and (not opt.bypass_hooks):
hook = RepoHook('pre-upload', self.manifest.repo_hooks_project,
self.manifest.topdir, abort_if_user_denies=True)
pending_proj_names = [project.name for (project, avail) in pending]
pending_worktrees = [project.worktree for (project, avail) in pending]
try:
hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
worktree_list=pending_worktrees)
except HookError as e:
print("ERROR: %s" % str(e), file=sys.stderr)
return
if opt.reviewers:
reviewers = _SplitEmails(opt.reviewers)
if opt.cc:
cc = _SplitEmails(opt.cc)
people = (reviewers, cc)
pending = self.ExecuteInParallel(
opt.jobs,
functools.partial(self._GatherOne, opt),
projects,
callback=_ProcessResults)
if not pending:
print("no branches ready for upload", file=sys.stderr)
elif len(pending) == 1 and len(pending[0][1]) == 1:
if opt.branch is None:
print('repo: error: no branches ready for upload', file=sys.stderr)
else:
print('repo: error: no branches named "%s" ready for upload' %
(opt.branch,), file=sys.stderr)
return 1
manifests = {project.manifest.topdir: project.manifest
for (project, available) in pending}
ret = 0
for manifest in manifests.values():
pending_proj_names = [project.name for (project, available) in pending
if project.manifest.topdir == manifest.topdir]
pending_worktrees = [project.worktree for (project, available) in pending
if project.manifest.topdir == manifest.topdir]
hook = RepoHook.FromSubcmd(
hook_type='pre-upload', manifest=manifest,
opt=opt, abort_if_user_denies=True)
if not hook.Run(
project_list=pending_proj_names,
worktree_list=pending_worktrees):
ret = 1
if ret:
return ret
reviewers = _SplitEmails(opt.reviewers) if opt.reviewers else []
cc = _SplitEmails(opt.cc) if opt.cc else []
people = (reviewers, cc)
if len(pending) == 1 and len(pending[0][1]) == 1:
self._SingleBranch(opt, pending[0][1][0], people)
else:
self._MultipleBranches(opt, pending, people)

View File

@ -1,4 +1,3 @@
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -13,17 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import platform
import sys
from command import Command, MirrorSafeCommand
from git_command import git
from git_command import git, RepoSourceVersion, user_agent
from git_refs import HEAD
from wrapper import Wrapper
class Version(Command, MirrorSafeCommand):
wrapper_version = None
wrapper_path = None
common = False
COMMON = False
helpSummary = "Display the version of repo"
helpUsage = """
%prog
@ -31,14 +33,34 @@ class Version(Command, MirrorSafeCommand):
def Execute(self, opt, args):
rp = self.manifest.repoProject
rem = rp.GetRemote(rp.remote.name)
rem = rp.GetRemote()
branch = rp.GetBranch('default')
print('repo version %s' % rp.work_git.describe(HEAD))
# These might not be the same. Report them both.
src_ver = RepoSourceVersion()
rp_ver = rp.bare_git.describe(HEAD)
print('repo version %s' % rp_ver)
print(' (from %s)' % rem.url)
print(' (tracking %s)' % branch.merge)
print(' (%s)' % rp.bare_git.log('-1', '--format=%cD', HEAD))
if Version.wrapper_path is not None:
print('repo launcher version %s' % Version.wrapper_version)
print(' (from %s)' % Version.wrapper_path)
if self.wrapper_path is not None:
print('repo launcher version %s' % self.wrapper_version)
print(' (from %s)' % self.wrapper_path)
print(git.version().strip())
if src_ver != rp_ver:
print(' (currently at %s)' % src_ver)
print('repo User-Agent %s' % user_agent.repo)
print('git %s' % git.version_tuple().full)
print('git User-Agent %s' % user_agent.git)
print('Python %s' % sys.version)
uname = platform.uname()
if sys.version_info.major < 3:
# Python 3 returns a named tuple, but Python 2 is simpler.
print(uname)
else:
print('OS %s %s (%s)' % (uname.system, uname.release, uname.version))
print('CPU %s (%s)' %
(uname.machine, uname.processor if uname.processor else 'unknown'))
print('Bug reports:', Wrapper().BUG_URL)

2
tests/fixtures/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/.repo_not.present.gitconfig.json
/.repo_test.gitconfig.json

1
tests/fixtures/gitc_config vendored Normal file
View File

@ -0,0 +1 @@
gitc_dir=/test/usr/local/google/gitc

View File

@ -1,3 +1,13 @@
[section]
empty
nonempty = true
boolinvalid = oops
booltrue = true
boolfalse = false
intinvalid = oops
inthex = 0x10
inthexk = 0x10k
int = 10
intk = 10k
intm = 10m
intg = 10g

56
tests/test_editor.py Normal file
View File

@ -0,0 +1,56 @@
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the editor.py module."""
import unittest
from editor import Editor
class EditorTestCase(unittest.TestCase):
"""Take care of resetting Editor state across tests."""
def setUp(self):
self.setEditor(None)
def tearDown(self):
self.setEditor(None)
@staticmethod
def setEditor(editor):
Editor._editor = editor
class GetEditor(EditorTestCase):
"""Check GetEditor behavior."""
def test_basic(self):
"""Basic checking of _GetEditor."""
self.setEditor(':')
self.assertEqual(':', Editor._GetEditor())
class EditString(EditorTestCase):
"""Check EditString behavior."""
def test_no_editor(self):
"""Check behavior when no editor is available."""
self.setEditor(':')
self.assertEqual('foo', Editor.EditString('foo'))
def test_cat_editor(self):
"""Check behavior when editor is `cat`."""
self.setEditor('cat')
self.assertEqual('foo', Editor.EditString('foo'))

53
tests/test_error.py Normal file
View File

@ -0,0 +1,53 @@
# Copyright 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the error.py module."""
import inspect
import pickle
import unittest
import error
class PickleTests(unittest.TestCase):
"""Make sure all our custom exceptions can be pickled."""
def getExceptions(self):
"""Return all our custom exceptions."""
for name in dir(error):
cls = getattr(error, name)
if isinstance(cls, type) and issubclass(cls, Exception):
yield cls
def testExceptionLookup(self):
"""Make sure our introspection logic works."""
classes = list(self.getExceptions())
self.assertIn(error.HookError, classes)
# Don't assert the exact number to avoid being a change-detector test.
self.assertGreater(len(classes), 10)
def testPickle(self):
"""Try to pickle all the exceptions."""
for cls in self.getExceptions():
args = inspect.getfullargspec(cls.__init__).args[1:]
obj = cls(*args)
p = pickle.dumps(obj)
try:
newobj = pickle.loads(p)
except Exception as e: # pylint: disable=broad-except
self.fail('Class %s is unable to be pickled: %s\n'
'Incomplete super().__init__(...) call?' % (cls, e))
self.assertIsInstance(newobj, cls)
self.assertEqual(str(obj), str(newobj))

122
tests/test_git_command.py Normal file
View File

@ -0,0 +1,122 @@
# Copyright 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the git_command.py module."""
import re
import unittest
try:
from unittest import mock
except ImportError:
import mock
import git_command
import wrapper
class GitCallUnitTest(unittest.TestCase):
"""Tests the _GitCall class (via git_command.git)."""
def test_version_tuple(self):
"""Check git.version_tuple() handling."""
ver = git_command.git.version_tuple()
self.assertIsNotNone(ver)
# We don't dive too deep into the values here to avoid having to update
# whenever git versions change. We do check relative to this min version
# as this is what `repo` itself requires via MIN_GIT_VERSION.
MIN_GIT_VERSION = (2, 10, 2)
self.assertTrue(isinstance(ver.major, int))
self.assertTrue(isinstance(ver.minor, int))
self.assertTrue(isinstance(ver.micro, int))
self.assertGreater(ver.major, MIN_GIT_VERSION[0] - 1)
self.assertGreaterEqual(ver.micro, 0)
self.assertGreaterEqual(ver.major, 0)
self.assertGreaterEqual(ver, MIN_GIT_VERSION)
self.assertLess(ver, (9999, 9999, 9999))
self.assertNotEqual('', ver.full)
class UserAgentUnitTest(unittest.TestCase):
"""Tests the UserAgent function."""
def test_smoke_os(self):
"""Make sure UA OS setting returns something useful."""
os_name = git_command.user_agent.os
# We can't dive too deep because of OS/tool differences, but we can check
# the general form.
m = re.match(r'^[^ ]+$', os_name)
self.assertIsNotNone(m)
def test_smoke_repo(self):
"""Make sure repo UA returns something useful."""
ua = git_command.user_agent.repo
# We can't dive too deep because of OS/tool differences, but we can check
# the general form.
m = re.match(r'^git-repo/[^ ]+ ([^ ]+) git/[^ ]+ Python/[0-9.]+', ua)
self.assertIsNotNone(m)
def test_smoke_git(self):
"""Make sure git UA returns something useful."""
ua = git_command.user_agent.git
# We can't dive too deep because of OS/tool differences, but we can check
# the general form.
m = re.match(r'^git/[^ ]+ ([^ ]+) git-repo/[^ ]+', ua)
self.assertIsNotNone(m)
class GitRequireTests(unittest.TestCase):
"""Test the git_require helper."""
def setUp(self):
ver = wrapper.GitVersion(1, 2, 3, 4)
mock.patch.object(git_command.git, 'version_tuple', return_value=ver).start()
def tearDown(self):
mock.patch.stopall()
def test_older_nonfatal(self):
"""Test non-fatal require calls with old versions."""
self.assertFalse(git_command.git_require((2,)))
self.assertFalse(git_command.git_require((1, 3)))
self.assertFalse(git_command.git_require((1, 2, 4)))
self.assertFalse(git_command.git_require((1, 2, 3, 5)))
def test_newer_nonfatal(self):
"""Test non-fatal require calls with newer versions."""
self.assertTrue(git_command.git_require((0,)))
self.assertTrue(git_command.git_require((1, 0)))
self.assertTrue(git_command.git_require((1, 2, 0)))
self.assertTrue(git_command.git_require((1, 2, 3, 0)))
def test_equal_nonfatal(self):
"""Test require calls with equal values."""
self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=False))
self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=True))
def test_older_fatal(self):
"""Test fatal require calls with old versions."""
with self.assertRaises(SystemExit) as e:
git_command.git_require((2,), fail=True)
self.assertNotEqual(0, e.code)
def test_older_fatal_msg(self):
"""Test fatal require calls with old versions and message."""
with self.assertRaises(SystemExit) as e:
git_command.git_require((2,), fail=True, msg='so sad')
self.assertNotEqual(0, e.code)

View File

@ -1,16 +1,35 @@
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the git_config.py module."""
import os
import tempfile
import unittest
import git_config
def fixture(*paths):
"""Return a path relative to test/fixtures.
"""
return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
class GitConfigUnitTest(unittest.TestCase):
"""Tests the GitConfig class.
"""
class GitConfigReadOnlyTests(unittest.TestCase):
"""Read-only tests of the GitConfig class."""
def setUp(self):
"""Create a GitConfig object using the test.gitconfig fixture.
"""
@ -48,5 +67,126 @@ class GitConfigUnitTest(unittest.TestCase):
val = config.GetString('empty')
self.assertEqual(val, None)
def test_GetBoolean_undefined(self):
"""Test GetBoolean on key that doesn't exist."""
self.assertIsNone(self.config.GetBoolean('section.missing'))
def test_GetBoolean_invalid(self):
"""Test GetBoolean on invalid boolean value."""
self.assertIsNone(self.config.GetBoolean('section.boolinvalid'))
def test_GetBoolean_true(self):
"""Test GetBoolean on valid true boolean."""
self.assertTrue(self.config.GetBoolean('section.booltrue'))
def test_GetBoolean_false(self):
"""Test GetBoolean on valid false boolean."""
self.assertFalse(self.config.GetBoolean('section.boolfalse'))
def test_GetInt_undefined(self):
"""Test GetInt on key that doesn't exist."""
self.assertIsNone(self.config.GetInt('section.missing'))
def test_GetInt_invalid(self):
"""Test GetInt on invalid integer value."""
self.assertIsNone(self.config.GetBoolean('section.intinvalid'))
def test_GetInt_valid(self):
"""Test GetInt on valid integers."""
TESTS = (
('inthex', 16),
('inthexk', 16384),
('int', 10),
('intk', 10240),
('intm', 10485760),
('intg', 10737418240),
)
for key, value in TESTS:
self.assertEqual(value, self.config.GetInt('section.%s' % (key,)))
class GitConfigReadWriteTests(unittest.TestCase):
"""Read/write tests of the GitConfig class."""
def setUp(self):
self.tmpfile = tempfile.NamedTemporaryFile()
self.config = self.get_config()
def get_config(self):
"""Get a new GitConfig instance."""
return git_config.GitConfig(self.tmpfile.name)
def test_SetString(self):
"""Test SetString behavior."""
# Set a value.
self.assertIsNone(self.config.GetString('foo.bar'))
self.config.SetString('foo.bar', 'val')
self.assertEqual('val', self.config.GetString('foo.bar'))
# Make sure the value was actually written out.
config = self.get_config()
self.assertEqual('val', config.GetString('foo.bar'))
# Update the value.
self.config.SetString('foo.bar', 'valll')
self.assertEqual('valll', self.config.GetString('foo.bar'))
config = self.get_config()
self.assertEqual('valll', config.GetString('foo.bar'))
# Delete the value.
self.config.SetString('foo.bar', None)
self.assertIsNone(self.config.GetString('foo.bar'))
config = self.get_config()
self.assertIsNone(config.GetString('foo.bar'))
def test_SetBoolean(self):
"""Test SetBoolean behavior."""
# Set a true value.
self.assertIsNone(self.config.GetBoolean('foo.bar'))
for val in (True, 1):
self.config.SetBoolean('foo.bar', val)
self.assertTrue(self.config.GetBoolean('foo.bar'))
# Make sure the value was actually written out.
config = self.get_config()
self.assertTrue(config.GetBoolean('foo.bar'))
self.assertEqual('true', config.GetString('foo.bar'))
# Set a false value.
for val in (False, 0):
self.config.SetBoolean('foo.bar', val)
self.assertFalse(self.config.GetBoolean('foo.bar'))
# Make sure the value was actually written out.
config = self.get_config()
self.assertFalse(config.GetBoolean('foo.bar'))
self.assertEqual('false', config.GetString('foo.bar'))
# Delete the value.
self.config.SetBoolean('foo.bar', None)
self.assertIsNone(self.config.GetBoolean('foo.bar'))
config = self.get_config()
self.assertIsNone(config.GetBoolean('foo.bar'))
def test_GetSyncAnalysisStateData(self):
"""Test config entries with a sync state analysis data."""
superproject_logging_data = {}
superproject_logging_data['test'] = False
options = type('options', (object,), {})()
options.verbose = 'true'
options.mp_update = 'false'
TESTS = (
('superproject.test', 'false'),
('options.verbose', 'true'),
('options.mpupdate', 'false'),
('main.version', '1'),
)
self.config.UpdateSyncAnalysisState(options, superproject_logging_data)
sync_data = self.config.GetSyncAnalysisStateData()
for key, value in TESTS:
self.assertEqual(sync_data[f'{git_config.SYNC_STATE_PREFIX}{key}'], value)
self.assertTrue(sync_data[f'{git_config.SYNC_STATE_PREFIX}main.synctime'])
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,370 @@
# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the git_superproject.py module."""
import json
import os
import platform
import tempfile
import unittest
from unittest import mock
import git_superproject
import git_trace2_event_log
import manifest_xml
from test_manifest_xml import sort_attributes
class SuperprojectTestCase(unittest.TestCase):
"""TestCase for the Superproject module."""
PARENT_SID_KEY = 'GIT_TRACE2_PARENT_SID'
PARENT_SID_VALUE = 'parent_sid'
SELF_SID_REGEX = r'repo-\d+T\d+Z-.*'
FULL_SID_REGEX = r'^%s/%s' % (PARENT_SID_VALUE, SELF_SID_REGEX)
def setUp(self):
"""Set up superproject every time."""
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
self.tempdir = self.tempdirobj.name
self.repodir = os.path.join(self.tempdir, '.repo')
self.manifest_file = os.path.join(
self.repodir, manifest_xml.MANIFEST_FILE_NAME)
os.mkdir(self.repodir)
self.platform = platform.system().lower()
# By default we initialize with the expected case where
# repo launches us (so GIT_TRACE2_PARENT_SID is set).
env = {
self.PARENT_SID_KEY: self.PARENT_SID_VALUE,
}
self.git_event_log = git_trace2_event_log.EventLog(env=env)
# The manifest parsing really wants a git repo currently.
gitdir = os.path.join(self.repodir, 'manifests.git')
os.mkdir(gitdir)
with open(os.path.join(gitdir, 'config'), 'w') as fp:
fp.write("""[remote "origin"]
url = https://localhost:0/manifest
""")
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<superproject name="superproject"/>
<project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """
" /></manifest>
""")
self._superproject = git_superproject.Superproject(
manifest, name='superproject',
remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'),
revision='refs/heads/main')
def tearDown(self):
"""Tear down superproject every time."""
self.tempdirobj.cleanup()
def getXmlManifest(self, data):
"""Helper to initialize a manifest for testing."""
with open(self.manifest_file, 'w') as fp:
fp.write(data)
return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
def verifyCommonKeys(self, log_entry, expected_event_name, full_sid=True):
"""Helper function to verify common event log keys."""
self.assertIn('event', log_entry)
self.assertIn('sid', log_entry)
self.assertIn('thread', log_entry)
self.assertIn('time', log_entry)
# Do basic data format validation.
self.assertEqual(expected_event_name, log_entry['event'])
if full_sid:
self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX)
else:
self.assertRegex(log_entry['sid'], self.SELF_SID_REGEX)
self.assertRegex(log_entry['time'], r'^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$')
def readLog(self, log_path):
"""Helper function to read log data into a list."""
log_data = []
with open(log_path, mode='rb') as f:
for line in f:
log_data.append(json.loads(line))
return log_data
def verifyErrorEvent(self):
"""Helper to verify that error event is written."""
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self.git_event_log.Write(path=tempdir)
self.log_data = self.readLog(log_path)
self.assertEqual(len(self.log_data), 2)
error_event = self.log_data[1]
self.verifyCommonKeys(self.log_data[0], expected_event_name='version')
self.verifyCommonKeys(error_event, expected_event_name='error')
# Check for 'error' event specific fields.
self.assertIn('msg', error_event)
self.assertIn('fmt', error_event)
def test_superproject_get_superproject_no_superproject(self):
"""Test with no url."""
manifest = self.getXmlManifest("""
<manifest>
</manifest>
""")
self.assertIsNone(manifest.superproject)
def test_superproject_get_superproject_invalid_url(self):
"""Test with an invalid url."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<superproject name="superproject"/>
</manifest>
""")
superproject = git_superproject.Superproject(
manifest, name='superproject',
remote=manifest.remotes.get('test-remote').ToRemoteSpec('superproject'),
revision='refs/heads/main')
sync_result = superproject.Sync(self.git_event_log)
self.assertFalse(sync_result.success)
self.assertTrue(sync_result.fatal)
def test_superproject_get_superproject_invalid_branch(self):
"""Test with an invalid branch."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<superproject name="superproject"/>
</manifest>
""")
self._superproject = git_superproject.Superproject(
manifest, name='superproject',
remote=manifest.remotes.get('test-remote').ToRemoteSpec('superproject'),
revision='refs/heads/main')
with mock.patch.object(self._superproject, '_branch', 'junk'):
sync_result = self._superproject.Sync(self.git_event_log)
self.assertFalse(sync_result.success)
self.assertTrue(sync_result.fatal)
def test_superproject_get_superproject_mock_init(self):
"""Test with _Init failing."""
with mock.patch.object(self._superproject, '_Init', return_value=False):
sync_result = self._superproject.Sync(self.git_event_log)
self.assertFalse(sync_result.success)
self.assertTrue(sync_result.fatal)
def test_superproject_get_superproject_mock_fetch(self):
"""Test with _Fetch failing."""
with mock.patch.object(self._superproject, '_Init', return_value=True):
os.mkdir(self._superproject._superproject_path)
with mock.patch.object(self._superproject, '_Fetch', return_value=False):
sync_result = self._superproject.Sync(self.git_event_log)
self.assertFalse(sync_result.success)
self.assertTrue(sync_result.fatal)
def test_superproject_get_all_project_commit_ids_mock_ls_tree(self):
"""Test with LsTree being a mock."""
data = ('120000 blob 158258bdf146f159218e2b90f8b699c4d85b5804\tAndroid.bp\x00'
'160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
'160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00'
'120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00'
'160000 commit ade9b7a0d874e25fff4bf2552488825c6f111928\tbuild/bazel\x00')
with mock.patch.object(self._superproject, '_Init', return_value=True):
with mock.patch.object(self._superproject, '_Fetch', return_value=True):
with mock.patch.object(self._superproject, '_LsTree', return_value=data):
commit_ids_result = self._superproject._GetAllProjectsCommitIds()
self.assertEqual(commit_ids_result.commit_ids, {
'art': '2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea',
'bootable/recovery': 'e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06',
'build/bazel': 'ade9b7a0d874e25fff4bf2552488825c6f111928'
})
self.assertFalse(commit_ids_result.fatal)
def test_superproject_write_manifest_file(self):
"""Test with writing manifest to a file after setting revisionId."""
self.assertEqual(len(self._superproject._manifest.projects), 1)
project = self._superproject._manifest.projects[0]
project.SetRevisionId('ABCDEF')
# Create temporary directory so that it can write the file.
os.mkdir(self._superproject._superproject_path)
manifest_path = self._superproject._WriteManifestFile()
self.assertIsNotNone(manifest_path)
with open(manifest_path, 'r') as fp:
manifest_xml_data = fp.read()
self.assertEqual(
sort_attributes(manifest_xml_data),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<project groups="notdefault,platform-' + self.platform + '" '
'name="platform/art" path="art" revision="ABCDEF" upstream="refs/heads/main"/>'
'<superproject name="superproject"/>'
'</manifest>')
def test_superproject_update_project_revision_id(self):
"""Test with LsTree being a mock."""
self.assertEqual(len(self._superproject._manifest.projects), 1)
projects = self._superproject._manifest.projects
data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
'160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00')
with mock.patch.object(self._superproject, '_Init', return_value=True):
with mock.patch.object(self._superproject, '_Fetch', return_value=True):
with mock.patch.object(self._superproject,
'_LsTree',
return_value=data):
# Create temporary directory so that it can write the file.
os.mkdir(self._superproject._superproject_path)
update_result = self._superproject.UpdateProjectsRevisionId(projects, self.git_event_log)
self.assertIsNotNone(update_result.manifest_path)
self.assertFalse(update_result.fatal)
with open(update_result.manifest_path, 'r') as fp:
manifest_xml_data = fp.read()
self.assertEqual(
sort_attributes(manifest_xml_data),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<project groups="notdefault,platform-' + self.platform + '" '
'name="platform/art" path="art" '
'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
'<superproject name="superproject"/>'
'</manifest>')
def test_superproject_update_project_revision_id_no_superproject_tag(self):
"""Test update of commit ids of a manifest without superproject tag."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="test-name"/>
</manifest>
""")
self.maxDiff = None
self.assertIsNone(manifest.superproject)
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<project name="test-name"/>'
'</manifest>')
def test_superproject_update_project_revision_id_from_local_manifest_group(self):
"""Test update of commit ids of a manifest that have local manifest no superproject group."""
local_group = manifest_xml.LOCAL_MANIFEST_GROUP_PREFIX + ':local'
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<remote name="goog" fetch="http://localhost2" />
<default remote="default-remote" revision="refs/heads/main" />
<superproject name="superproject"/>
<project path="vendor/x" name="platform/vendor/x" remote="goog"
groups=\"""" + local_group + """
" revision="master-with-vendor" clone-depth="1" />
<project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """
" /></manifest>
""")
self.maxDiff = None
self._superproject = git_superproject.Superproject(
manifest, name='superproject',
remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'),
revision='refs/heads/main')
self.assertEqual(len(self._superproject._manifest.projects), 2)
projects = self._superproject._manifest.projects
data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00')
with mock.patch.object(self._superproject, '_Init', return_value=True):
with mock.patch.object(self._superproject, '_Fetch', return_value=True):
with mock.patch.object(self._superproject,
'_LsTree',
return_value=data):
# Create temporary directory so that it can write the file.
os.mkdir(self._superproject._superproject_path)
update_result = self._superproject.UpdateProjectsRevisionId(projects, self.git_event_log)
self.assertIsNotNone(update_result.manifest_path)
self.assertFalse(update_result.fatal)
with open(update_result.manifest_path, 'r') as fp:
manifest_xml_data = fp.read()
# Verify platform/vendor/x's project revision hasn't changed.
self.assertEqual(
sort_attributes(manifest_xml_data),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<remote fetch="http://localhost2" name="goog"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<project groups="notdefault,platform-' + self.platform + '" '
'name="platform/art" path="art" '
'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
'<superproject name="superproject"/>'
'</manifest>')
def test_superproject_update_project_revision_id_with_pinned_manifest(self):
"""Test update of commit ids of a pinned manifest."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<superproject name="superproject"/>
<project path="vendor/x" name="platform/vendor/x" revision="" />
<project path="vendor/y" name="platform/vendor/y"
revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f" />
<project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """
" /></manifest>
""")
self.maxDiff = None
self._superproject = git_superproject.Superproject(
manifest, name='superproject',
remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'),
revision='refs/heads/main')
self.assertEqual(len(self._superproject._manifest.projects), 3)
projects = self._superproject._manifest.projects
data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
'160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tvendor/x\x00')
with mock.patch.object(self._superproject, '_Init', return_value=True):
with mock.patch.object(self._superproject, '_Fetch', return_value=True):
with mock.patch.object(self._superproject,
'_LsTree',
return_value=data):
# Create temporary directory so that it can write the file.
os.mkdir(self._superproject._superproject_path)
update_result = self._superproject.UpdateProjectsRevisionId(projects, self.git_event_log)
self.assertIsNotNone(update_result.manifest_path)
self.assertFalse(update_result.fatal)
with open(update_result.manifest_path, 'r') as fp:
manifest_xml_data = fp.read()
# Verify platform/vendor/x's project revision hasn't changed.
self.assertEqual(
sort_attributes(manifest_xml_data),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<project groups="notdefault,platform-' + self.platform + '" '
'name="platform/art" path="art" '
'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
'<project name="platform/vendor/x" path="vendor/x" '
'revision="e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06" upstream="refs/heads/main"/>'
'<project name="platform/vendor/y" path="vendor/y" '
'revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f"/>'
'<superproject name="superproject"/>'
'</manifest>')
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,391 @@
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the git_trace2_event_log.py module."""
import json
import os
import socket
import tempfile
import threading
import unittest
from unittest import mock
import git_trace2_event_log
import platform_utils
def serverLoggingThread(socket_path, server_ready, received_traces):
"""Helper function to receive logs over a Unix domain socket.
Appends received messages on the provided socket and appends to received_traces.
Args:
socket_path: path to a Unix domain socket on which to listen for traces
server_ready: a threading.Condition used to signal to the caller that this thread is ready to
accept connections
received_traces: a list to which received traces will be appended (after decoding to a utf-8
string).
"""
platform_utils.remove(socket_path, missing_ok=True)
data = b''
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.bind(socket_path)
sock.listen(0)
with server_ready:
server_ready.notify()
with sock.accept()[0] as conn:
while True:
recved = conn.recv(4096)
if not recved:
break
data += recved
received_traces.extend(data.decode('utf-8').splitlines())
class EventLogTestCase(unittest.TestCase):
"""TestCase for the EventLog module."""
PARENT_SID_KEY = 'GIT_TRACE2_PARENT_SID'
PARENT_SID_VALUE = 'parent_sid'
SELF_SID_REGEX = r'repo-\d+T\d+Z-.*'
FULL_SID_REGEX = r'^%s/%s' % (PARENT_SID_VALUE, SELF_SID_REGEX)
def setUp(self):
"""Load the event_log module every time."""
self._event_log_module = None
# By default we initialize with the expected case where
# repo launches us (so GIT_TRACE2_PARENT_SID is set).
env = {
self.PARENT_SID_KEY: self.PARENT_SID_VALUE,
}
self._event_log_module = git_trace2_event_log.EventLog(env=env)
self._log_data = None
def verifyCommonKeys(self, log_entry, expected_event_name=None, full_sid=True):
"""Helper function to verify common event log keys."""
self.assertIn('event', log_entry)
self.assertIn('sid', log_entry)
self.assertIn('thread', log_entry)
self.assertIn('time', log_entry)
# Do basic data format validation.
if expected_event_name:
self.assertEqual(expected_event_name, log_entry['event'])
if full_sid:
self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX)
else:
self.assertRegex(log_entry['sid'], self.SELF_SID_REGEX)
self.assertRegex(log_entry['time'], r'^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$')
def readLog(self, log_path):
"""Helper function to read log data into a list."""
log_data = []
with open(log_path, mode='rb') as f:
for line in f:
log_data.append(json.loads(line))
return log_data
def remove_prefix(self, s, prefix):
"""Return a copy string after removing |prefix| from |s|, if present or the original string."""
if s.startswith(prefix):
return s[len(prefix):]
else:
return s
def test_initial_state_with_parent_sid(self):
"""Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent."""
self.assertRegex(self._event_log_module.full_sid, self.FULL_SID_REGEX)
def test_initial_state_no_parent_sid(self):
"""Test initial state when 'GIT_TRACE2_PARENT_SID' is not set."""
# Setup an empty environment dict (no parent sid).
self._event_log_module = git_trace2_event_log.EventLog(env={})
self.assertRegex(self._event_log_module.full_sid, self.SELF_SID_REGEX)
def test_version_event(self):
"""Test 'version' event data is valid.
Verify that the 'version' event is written even when no other
events are addded.
Expected event log:
<version event>
"""
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
# A log with no added events should only have the version entry.
self.assertEqual(len(self._log_data), 1)
version_event = self._log_data[0]
self.verifyCommonKeys(version_event, expected_event_name='version')
# Check for 'version' event specific fields.
self.assertIn('evt', version_event)
self.assertIn('exe', version_event)
# Verify "evt" version field is a string.
self.assertIsInstance(version_event['evt'], str)
def test_start_event(self):
"""Test and validate 'start' event data is valid.
Expected event log:
<version event>
<start event>
"""
self._event_log_module.StartEvent()
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 2)
start_event = self._log_data[1]
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
self.verifyCommonKeys(start_event, expected_event_name='start')
# Check for 'start' event specific fields.
self.assertIn('argv', start_event)
self.assertTrue(isinstance(start_event['argv'], list))
def test_exit_event_result_none(self):
"""Test 'exit' event data is valid when result is None.
We expect None result to be converted to 0 in the exit event data.
Expected event log:
<version event>
<exit event>
"""
self._event_log_module.ExitEvent(None)
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 2)
exit_event = self._log_data[1]
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
self.verifyCommonKeys(exit_event, expected_event_name='exit')
# Check for 'exit' event specific fields.
self.assertIn('code', exit_event)
# 'None' result should convert to 0 (successful) return code.
self.assertEqual(exit_event['code'], 0)
def test_exit_event_result_integer(self):
"""Test 'exit' event data is valid when result is an integer.
Expected event log:
<version event>
<exit event>
"""
self._event_log_module.ExitEvent(2)
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 2)
exit_event = self._log_data[1]
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
self.verifyCommonKeys(exit_event, expected_event_name='exit')
# Check for 'exit' event specific fields.
self.assertIn('code', exit_event)
self.assertEqual(exit_event['code'], 2)
def test_command_event(self):
"""Test and validate 'command' event data is valid.
Expected event log:
<version event>
<command event>
"""
name = 'repo'
subcommands = ['init' 'this']
self._event_log_module.CommandEvent(name='repo', subcommands=subcommands)
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 2)
command_event = self._log_data[1]
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
self.verifyCommonKeys(command_event, expected_event_name='command')
# Check for 'command' event specific fields.
self.assertIn('name', command_event)
self.assertIn('subcommands', command_event)
self.assertEqual(command_event['name'], name)
self.assertEqual(command_event['subcommands'], subcommands)
def test_def_params_event_repo_config(self):
"""Test 'def_params' event data outputs only repo config keys.
Expected event log:
<version event>
<def_param event>
<def_param event>
"""
config = {
'git.foo': 'bar',
'repo.partialclone': 'true',
'repo.partialclonefilter': 'blob:none',
}
self._event_log_module.DefParamRepoEvents(config)
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 3)
def_param_events = self._log_data[1:]
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
for event in def_param_events:
self.verifyCommonKeys(event, expected_event_name='def_param')
# Check for 'def_param' event specific fields.
self.assertIn('param', event)
self.assertIn('value', event)
self.assertTrue(event['param'].startswith('repo.'))
def test_def_params_event_no_repo_config(self):
"""Test 'def_params' event data won't output non-repo config keys.
Expected event log:
<version event>
"""
config = {
'git.foo': 'bar',
'git.core.foo2': 'baz',
}
self._event_log_module.DefParamRepoEvents(config)
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 1)
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
def test_data_event_config(self):
"""Test 'data' event data outputs all config keys.
Expected event log:
<version event>
<data event>
<data event>
"""
config = {
'git.foo': 'bar',
'repo.partialclone': 'false',
'repo.syncstate.superproject.hassuperprojecttag': 'true',
'repo.syncstate.superproject.sys.argv': ['--', 'sync', 'protobuf'],
}
prefix_value = 'prefix'
self._event_log_module.LogDataConfigEvents(config, prefix_value)
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 5)
data_events = self._log_data[1:]
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
for event in data_events:
self.verifyCommonKeys(event)
# Check for 'data' event specific fields.
self.assertIn('key', event)
self.assertIn('value', event)
key = event['key']
key = self.remove_prefix(key, f'{prefix_value}/')
value = event['value']
self.assertEqual(self._event_log_module.GetDataEventName(value), event['event'])
self.assertTrue(key in config and value == config[key])
def test_error_event(self):
"""Test and validate 'error' event data is valid.
Expected event log:
<version event>
<error event>
"""
msg = 'invalid option: --cahced'
fmt = 'invalid option: %s'
self._event_log_module.ErrorEvent(msg, fmt)
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
log_path = self._event_log_module.Write(path=tempdir)
self._log_data = self.readLog(log_path)
self.assertEqual(len(self._log_data), 2)
error_event = self._log_data[1]
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
self.verifyCommonKeys(error_event, expected_event_name='error')
# Check for 'error' event specific fields.
self.assertIn('msg', error_event)
self.assertIn('fmt', error_event)
self.assertEqual(error_event['msg'], msg)
self.assertEqual(error_event['fmt'], fmt)
def test_write_with_filename(self):
"""Test Write() with a path to a file exits with None."""
self.assertIsNone(self._event_log_module.Write(path='path/to/file'))
def test_write_with_git_config(self):
"""Test Write() uses the git config path when 'git config' call succeeds."""
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
with mock.patch.object(self._event_log_module,
'_GetEventTargetPath', return_value=tempdir):
self.assertEqual(os.path.dirname(self._event_log_module.Write()), tempdir)
def test_write_no_git_config(self):
"""Test Write() with no git config variable present exits with None."""
with mock.patch.object(self._event_log_module,
'_GetEventTargetPath', return_value=None):
self.assertIsNone(self._event_log_module.Write())
def test_write_non_string(self):
"""Test Write() with non-string type for |path| throws TypeError."""
with self.assertRaises(TypeError):
self._event_log_module.Write(path=1234)
def test_write_socket(self):
"""Test Write() with Unix domain socket for |path| and validate received traces."""
received_traces = []
with tempfile.TemporaryDirectory(prefix='test_server_sockets') as tempdir:
socket_path = os.path.join(tempdir, "server.sock")
server_ready = threading.Condition()
# Start "server" listening on Unix domain socket at socket_path.
try:
server_thread = threading.Thread(
target=serverLoggingThread,
args=(socket_path, server_ready, received_traces))
server_thread.start()
with server_ready:
server_ready.wait()
self._event_log_module.StartEvent()
path = self._event_log_module.Write(path=f'af_unix:{socket_path}')
finally:
server_thread.join(timeout=5)
self.assertEqual(path, f'af_unix:stream:{socket_path}')
self.assertEqual(len(received_traces), 2)
version_event = json.loads(received_traces[0])
start_event = json.loads(received_traces[1])
self.verifyCommonKeys(version_event, expected_event_name='version')
self.verifyCommonKeys(start_event, expected_event_name='start')
# Check for 'start' event specific fields.
self.assertIn('argv', start_event)
self.assertIsInstance(start_event['argv'], list)
if __name__ == '__main__':
unittest.main()

55
tests/test_hooks.py Normal file
View File

@ -0,0 +1,55 @@
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the hooks.py module."""
import hooks
import unittest
class RepoHookShebang(unittest.TestCase):
"""Check shebang parsing in RepoHook."""
def test_no_shebang(self):
"""Lines w/out shebangs should be rejected."""
DATA = (
'',
'#\n# foo\n',
'# Bad shebang in script\n#!/foo\n'
)
for data in DATA:
self.assertIsNone(hooks.RepoHook._ExtractInterpFromShebang(data))
def test_direct_interp(self):
"""Lines whose shebang points directly to the interpreter."""
DATA = (
('#!/foo', '/foo'),
('#! /foo', '/foo'),
('#!/bin/foo ', '/bin/foo'),
('#! /usr/foo ', '/usr/foo'),
('#! /usr/foo -args', '/usr/foo'),
)
for shebang, interp in DATA:
self.assertEqual(hooks.RepoHook._ExtractInterpFromShebang(shebang),
interp)
def test_env_interp(self):
"""Lines whose shebang launches through `env`."""
DATA = (
('#!/usr/bin/env foo', 'foo'),
('#!/bin/env foo', 'foo'),
('#! /bin/env /bin/foo ', '/bin/foo'),
)
for shebang, interp in DATA:
self.assertEqual(hooks.RepoHook._ExtractInterpFromShebang(shebang),
interp)

900
tests/test_manifest_xml.py Normal file
View File

@ -0,0 +1,900 @@
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the manifest_xml.py module."""
import os
import platform
import re
import tempfile
import unittest
import xml.dom.minidom
import error
import manifest_xml
# Invalid paths that we don't want in the filesystem.
INVALID_FS_PATHS = (
'',
'.',
'..',
'../',
'./',
'.//',
'foo/',
'./foo',
'../foo',
'foo/./bar',
'foo/../../bar',
'/foo',
'./../foo',
'.git/foo',
# Check case folding.
'.GIT/foo',
'blah/.git/foo',
'.repo/foo',
'.repoconfig',
# Block ~ due to 8.3 filenames on Windows filesystems.
'~',
'foo~',
'blah/foo~',
# Block Unicode characters that get normalized out by filesystems.
u'foo\u200Cbar',
# Block newlines.
'f\n/bar',
'f\r/bar',
)
# Make sure platforms that use path separators (e.g. Windows) are also
# rejected properly.
if os.path.sep != '/':
INVALID_FS_PATHS += tuple(x.replace('/', os.path.sep) for x in INVALID_FS_PATHS)
def sort_attributes(manifest):
"""Sort the attributes of all elements alphabetically.
This is needed because different versions of the toxml() function from
xml.dom.minidom outputs the attributes of elements in different orders.
Before Python 3.8 they were output alphabetically, later versions preserve
the order specified by the user.
Args:
manifest: String containing an XML manifest.
Returns:
The XML manifest with the attributes of all elements sorted alphabetically.
"""
new_manifest = ''
# This will find every element in the XML manifest, whether they have
# attributes or not. This simplifies recreating the manifest below.
matches = re.findall(r'(<[/?]?[a-z-]+\s*)((?:\S+?="[^"]+"\s*?)*)(\s*[/?]?>)', manifest)
for head, attrs, tail in matches:
m = re.findall(r'\S+?="[^"]+"', attrs)
new_manifest += head + ' '.join(sorted(m)) + tail
return new_manifest
class ManifestParseTestCase(unittest.TestCase):
"""TestCase for parsing manifests."""
def setUp(self):
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
self.tempdir = self.tempdirobj.name
self.repodir = os.path.join(self.tempdir, '.repo')
self.manifest_dir = os.path.join(self.repodir, 'manifests')
self.manifest_file = os.path.join(
self.repodir, manifest_xml.MANIFEST_FILE_NAME)
self.local_manifest_dir = os.path.join(
self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME)
os.mkdir(self.repodir)
os.mkdir(self.manifest_dir)
# The manifest parsing really wants a git repo currently.
gitdir = os.path.join(self.repodir, 'manifests.git')
os.mkdir(gitdir)
with open(os.path.join(gitdir, 'config'), 'w') as fp:
fp.write("""[remote "origin"]
url = https://localhost:0/manifest
""")
def tearDown(self):
self.tempdirobj.cleanup()
def getXmlManifest(self, data):
"""Helper to initialize a manifest for testing."""
with open(self.manifest_file, 'w') as fp:
fp.write(data)
return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
@staticmethod
def encodeXmlAttr(attr):
"""Encode |attr| using XML escape rules."""
return attr.replace('\r', '&#x000d;').replace('\n', '&#x000a;')
class ManifestValidateFilePaths(unittest.TestCase):
"""Check _ValidateFilePaths helper.
This doesn't access a real filesystem.
"""
def check_both(self, *args):
manifest_xml.XmlManifest._ValidateFilePaths('copyfile', *args)
manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
def test_normal_path(self):
"""Make sure good paths are accepted."""
self.check_both('foo', 'bar')
self.check_both('foo/bar', 'bar')
self.check_both('foo', 'bar/bar')
self.check_both('foo/bar', 'bar/bar')
def test_symlink_targets(self):
"""Some extra checks for symlinks."""
def check(*args):
manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
# We allow symlinks to end in a slash since we allow them to point to dirs
# in general. Technically the slash isn't necessary.
check('foo/', 'bar')
# We allow a single '.' to get a reference to the project itself.
check('.', 'bar')
def test_bad_paths(self):
"""Make sure bad paths (src & dest) are rejected."""
for path in INVALID_FS_PATHS:
self.assertRaises(
error.ManifestInvalidPathError, self.check_both, path, 'a')
self.assertRaises(
error.ManifestInvalidPathError, self.check_both, 'a', path)
class ValueTests(unittest.TestCase):
"""Check utility parsing code."""
def _get_node(self, text):
return xml.dom.minidom.parseString(text).firstChild
def test_bool_default(self):
"""Check XmlBool default handling."""
node = self._get_node('<node/>')
self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
self.assertIsNone(manifest_xml.XmlBool(node, 'a', None))
self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
node = self._get_node('<node a=""/>')
self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
def test_bool_invalid(self):
"""Check XmlBool invalid handling."""
node = self._get_node('<node a="moo"/>')
self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
def test_bool_true(self):
"""Check XmlBool true values."""
for value in ('yes', 'true', '1'):
node = self._get_node('<node a="%s"/>' % (value,))
self.assertTrue(manifest_xml.XmlBool(node, 'a'))
def test_bool_false(self):
"""Check XmlBool false values."""
for value in ('no', 'false', '0'):
node = self._get_node('<node a="%s"/>' % (value,))
self.assertFalse(manifest_xml.XmlBool(node, 'a'))
def test_int_default(self):
"""Check XmlInt default handling."""
node = self._get_node('<node/>')
self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
self.assertIsNone(manifest_xml.XmlInt(node, 'a', None))
self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123))
node = self._get_node('<node a=""/>')
self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
def test_int_good(self):
"""Check XmlInt numeric handling."""
for value in (-1, 0, 1, 50000):
node = self._get_node('<node a="%s"/>' % (value,))
self.assertEqual(value, manifest_xml.XmlInt(node, 'a'))
def test_int_invalid(self):
"""Check XmlInt invalid handling."""
with self.assertRaises(error.ManifestParseError):
node = self._get_node('<node a="xx"/>')
manifest_xml.XmlInt(node, 'a')
class XmlManifestTests(ManifestParseTestCase):
"""Check manifest processing."""
def test_empty(self):
"""Parse an 'empty' manifest file."""
manifest = self.getXmlManifest(
'<?xml version="1.0" encoding="UTF-8"?>'
'<manifest></manifest>')
self.assertEqual(manifest.remotes, {})
self.assertEqual(manifest.projects, [])
def test_link(self):
"""Verify Link handling with new names."""
manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file)
with open(os.path.join(self.manifest_dir, 'foo.xml'), 'w') as fp:
fp.write('<manifest></manifest>')
manifest.Link('foo.xml')
with open(self.manifest_file) as fp:
self.assertIn('<include name="foo.xml" />', fp.read())
def test_toxml_empty(self):
"""Verify the ToXml() helper."""
manifest = self.getXmlManifest(
'<?xml version="1.0" encoding="UTF-8"?>'
'<manifest></manifest>')
self.assertEqual(manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>')
def test_todict_empty(self):
"""Verify the ToDict() helper."""
manifest = self.getXmlManifest(
'<?xml version="1.0" encoding="UTF-8"?>'
'<manifest></manifest>')
self.assertEqual(manifest.ToDict(), {})
def test_toxml_omit_local(self):
"""Does not include local_manifests projects when omit_local=True."""
manifest = self.getXmlManifest(
'<?xml version="1.0" encoding="UTF-8"?><manifest>'
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
'<project name="p" groups="local::me"/>'
'<project name="q"/>'
'<project name="r" groups="keep"/>'
'</manifest>')
self.assertEqual(
manifest.ToXml(omit_local=True).toxml(),
'<?xml version="1.0" ?><manifest>'
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
'<project name="q"/><project name="r" groups="keep"/></manifest>')
def test_toxml_with_local(self):
"""Does include local_manifests projects when omit_local=False."""
manifest = self.getXmlManifest(
'<?xml version="1.0" encoding="UTF-8"?><manifest>'
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
'<project name="p" groups="local::me"/>'
'<project name="q"/>'
'<project name="r" groups="keep"/>'
'</manifest>')
self.assertEqual(
manifest.ToXml(omit_local=False).toxml(),
'<?xml version="1.0" ?><manifest>'
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
'<project name="p" groups="local::me"/>'
'<project name="q"/><project name="r" groups="keep"/></manifest>')
def test_repo_hooks(self):
"""Check repo-hooks settings."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<project name="repohooks" path="src/repohooks"/>
<repo-hooks in-project="repohooks" enabled-list="a, b"/>
</manifest>
""")
self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
def test_repo_hooks_unordered(self):
"""Check repo-hooks settings work even if the project def comes second."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<repo-hooks in-project="repohooks" enabled-list="a, b"/>
<project name="repohooks" path="src/repohooks"/>
</manifest>
""")
self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
def test_unknown_tags(self):
"""Check superproject settings."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<superproject name="superproject"/>
<iankaz value="unknown (possible) future tags are ignored"/>
<x-custom-tag>X tags are always ignored</x-custom-tag>
</manifest>
""")
self.assertEqual(manifest.superproject.name, 'superproject')
self.assertEqual(manifest.superproject.remote.name, 'test-remote')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="test-remote"/>'
'<default remote="test-remote" revision="refs/heads/main"/>'
'<superproject name="superproject"/>'
'</manifest>')
def test_remote_annotations(self):
"""Check remote settings."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost">
<annotation name="foo" value="bar"/>
</remote>
</manifest>
""")
self.assertEqual(manifest.remotes['test-remote'].annotations[0].name, 'foo')
self.assertEqual(manifest.remotes['test-remote'].annotations[0].value, 'bar')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="test-remote">'
'<annotation name="foo" value="bar"/>'
'</remote>'
'</manifest>')
class IncludeElementTests(ManifestParseTestCase):
"""Tests for <include>."""
def test_group_levels(self):
root_m = os.path.join(self.manifest_dir, 'root.xml')
with open(root_m, 'w') as fp:
fp.write("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<include name="level1.xml" groups="level1-group" />
<project name="root-name1" path="root-path1" />
<project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
</manifest>
""")
with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
fp.write("""
<manifest>
<include name="level2.xml" groups="level2-group" />
<project name="level1-name1" path="level1-path1" />
</manifest>
""")
with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
fp.write("""
<manifest>
<project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
</manifest>
""")
include_m = manifest_xml.XmlManifest(self.repodir, root_m)
for proj in include_m.projects:
if proj.name == 'root-name1':
# Check include group not set on root level proj.
self.assertNotIn('level1-group', proj.groups)
if proj.name == 'root-name2':
# Check root proj group not removed.
self.assertIn('r2g1', proj.groups)
if proj.name == 'level1-name1':
# Check level1 proj has inherited group level 1.
self.assertIn('level1-group', proj.groups)
if proj.name == 'level2-name1':
# Check level2 proj has inherited group levels 1 and 2.
self.assertIn('level1-group', proj.groups)
self.assertIn('level2-group', proj.groups)
# Check level2 proj group not removed.
self.assertIn('l2g1', proj.groups)
def test_allow_bad_name_from_user(self):
"""Check handling of bad name attribute from the user's input."""
def parse(name):
name = self.encodeXmlAttr(name)
manifest = self.getXmlManifest(f"""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<include name="{name}" />
</manifest>
""")
# Force the manifest to be parsed.
manifest.ToXml()
# Setup target of the include.
target = os.path.join(self.tempdir, 'target.xml')
with open(target, 'w') as fp:
fp.write('<manifest></manifest>')
# Include with absolute path.
parse(os.path.abspath(target))
# Include with relative path.
parse(os.path.relpath(target, self.manifest_dir))
def test_bad_name_checks(self):
"""Check handling of bad name attribute."""
def parse(name):
name = self.encodeXmlAttr(name)
# Setup target of the include.
with open(os.path.join(self.manifest_dir, 'target.xml'), 'w') as fp:
fp.write(f'<manifest><include name="{name}"/></manifest>')
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<include name="target.xml" />
</manifest>
""")
# Force the manifest to be parsed.
manifest.ToXml()
# Handle empty name explicitly because a different codepath rejects it.
with self.assertRaises(error.ManifestParseError):
parse('')
for path in INVALID_FS_PATHS:
if not path:
continue
with self.assertRaises(error.ManifestInvalidPathError):
parse(path)
class ProjectElementTests(ManifestParseTestCase):
"""Tests for <project>."""
def test_group(self):
"""Check project group settings."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<project name="test-name" path="test-path"/>
<project name="extras" path="path" groups="g1,g2,g1"/>
</manifest>
""")
self.assertEqual(len(manifest.projects), 2)
# Ordering isn't guaranteed.
result = {
manifest.projects[0].name: manifest.projects[0].groups,
manifest.projects[1].name: manifest.projects[1].groups,
}
project = manifest.projects[0]
self.assertCountEqual(
result['test-name'],
['name:test-name', 'all', 'path:test-path'])
self.assertCountEqual(
result['extras'],
['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
groupstr = 'default,platform-' + platform.system().lower()
self.assertEqual(groupstr, manifest.GetGroupsStr())
groupstr = 'g1,g2,g1'
manifest.manifestProject.config.SetString('manifest.groups', groupstr)
self.assertEqual(groupstr, manifest.GetGroupsStr())
def test_set_revision_id(self):
"""Check setting of project's revisionId."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="test-name"/>
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
project = manifest.projects[0]
project.SetRevisionId('ABCDEF')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>'
'</manifest>')
def test_trailing_slash(self):
"""Check handling of trailing slashes in attributes."""
def parse(name, path):
name = self.encodeXmlAttr(name)
path = self.encodeXmlAttr(path)
return self.getXmlManifest(f"""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="{name}" path="{path}" />
</manifest>
""")
manifest = parse('a/path/', 'foo')
self.assertEqual(manifest.projects[0].gitdir,
os.path.join(self.tempdir, '.repo/projects/foo.git'))
self.assertEqual(manifest.projects[0].objdir,
os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
manifest = parse('a/path', 'foo/')
self.assertEqual(manifest.projects[0].gitdir,
os.path.join(self.tempdir, '.repo/projects/foo.git'))
self.assertEqual(manifest.projects[0].objdir,
os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
manifest = parse('a/path', 'foo//////')
self.assertEqual(manifest.projects[0].gitdir,
os.path.join(self.tempdir, '.repo/projects/foo.git'))
self.assertEqual(manifest.projects[0].objdir,
os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
def test_toplevel_path(self):
"""Check handling of path=. specially."""
def parse(name, path):
name = self.encodeXmlAttr(name)
path = self.encodeXmlAttr(path)
return self.getXmlManifest(f"""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="{name}" path="{path}" />
</manifest>
""")
for path in ('.', './', './/', './//'):
manifest = parse('server/path', path)
self.assertEqual(manifest.projects[0].gitdir,
os.path.join(self.tempdir, '.repo/projects/..git'))
def test_bad_path_name_checks(self):
"""Check handling of bad path & name attributes."""
def parse(name, path):
name = self.encodeXmlAttr(name)
path = self.encodeXmlAttr(path)
manifest = self.getXmlManifest(f"""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="{name}" path="{path}" />
</manifest>
""")
# Force the manifest to be parsed.
manifest.ToXml()
# Verify the parser is valid by default to avoid buggy tests below.
parse('ok', 'ok')
# Handle empty name explicitly because a different codepath rejects it.
# Empty path is OK because it defaults to the name field.
with self.assertRaises(error.ManifestParseError):
parse('', 'ok')
for path in INVALID_FS_PATHS:
if not path or path.endswith('/'):
continue
with self.assertRaises(error.ManifestInvalidPathError):
parse(path, 'ok')
# We have a dedicated test for path=".".
if path not in {'.'}:
with self.assertRaises(error.ManifestInvalidPathError):
parse('ok', path)
class SuperProjectElementTests(ManifestParseTestCase):
"""Tests for <superproject>."""
def test_superproject(self):
"""Check superproject settings."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<superproject name="superproject"/>
</manifest>
""")
self.assertEqual(manifest.superproject.name, 'superproject')
self.assertEqual(manifest.superproject.remote.name, 'test-remote')
self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="test-remote"/>'
'<default remote="test-remote" revision="refs/heads/main"/>'
'<superproject name="superproject"/>'
'</manifest>')
def test_superproject_revision(self):
"""Check superproject settings with a different revision attribute"""
self.maxDiff = None
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/main" />
<superproject name="superproject" revision="refs/heads/stable" />
</manifest>
""")
self.assertEqual(manifest.superproject.name, 'superproject')
self.assertEqual(manifest.superproject.remote.name, 'test-remote')
self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="test-remote"/>'
'<default remote="test-remote" revision="refs/heads/main"/>'
'<superproject name="superproject" revision="refs/heads/stable"/>'
'</manifest>')
def test_superproject_revision_default_negative(self):
"""Check superproject settings with a same revision attribute"""
self.maxDiff = None
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" />
<default remote="test-remote" revision="refs/heads/stable" />
<superproject name="superproject" revision="refs/heads/stable" />
</manifest>
""")
self.assertEqual(manifest.superproject.name, 'superproject')
self.assertEqual(manifest.superproject.remote.name, 'test-remote')
self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="test-remote"/>'
'<default remote="test-remote" revision="refs/heads/stable"/>'
'<superproject name="superproject"/>'
'</manifest>')
def test_superproject_revision_remote(self):
"""Check superproject settings with a same revision attribute"""
self.maxDiff = None
manifest = self.getXmlManifest("""
<manifest>
<remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" />
<default remote="test-remote" />
<superproject name="superproject" revision="refs/heads/stable" />
</manifest>
""")
self.assertEqual(manifest.superproject.name, 'superproject')
self.assertEqual(manifest.superproject.remote.name, 'test-remote')
self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>'
'<default remote="test-remote"/>'
'<superproject name="superproject" revision="refs/heads/stable"/>'
'</manifest>')
def test_remote(self):
"""Check superproject settings with a remote."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<remote name="superproject-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<superproject name="platform/superproject" remote="superproject-remote"/>
</manifest>
""")
self.assertEqual(manifest.superproject.name, 'platform/superproject')
self.assertEqual(manifest.superproject.remote.name, 'superproject-remote')
self.assertEqual(manifest.superproject.remote.url, 'http://localhost/platform/superproject')
self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<remote fetch="http://localhost" name="superproject-remote"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<superproject name="platform/superproject" remote="superproject-remote"/>'
'</manifest>')
def test_defalut_remote(self):
"""Check superproject settings with a default remote."""
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<superproject name="superproject" remote="default-remote"/>
</manifest>
""")
self.assertEqual(manifest.superproject.name, 'superproject')
self.assertEqual(manifest.superproject.remote.name, 'default-remote')
self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
self.assertEqual(
sort_attributes(manifest.ToXml().toxml()),
'<?xml version="1.0" ?><manifest>'
'<remote fetch="http://localhost" name="default-remote"/>'
'<default remote="default-remote" revision="refs/heads/main"/>'
'<superproject name="superproject"/>'
'</manifest>')
class ContactinfoElementTests(ManifestParseTestCase):
"""Tests for <contactinfo>."""
def test_contactinfo(self):
"""Check contactinfo settings."""
bugurl = 'http://localhost/contactinfo'
manifest = self.getXmlManifest(f"""
<manifest>
<contactinfo bugurl="{bugurl}"/>
</manifest>
""")
self.assertEqual(manifest.contactinfo.bugurl, bugurl)
self.assertEqual(
manifest.ToXml().toxml(),
'<?xml version="1.0" ?><manifest>'
f'<contactinfo bugurl="{bugurl}"/>'
'</manifest>')
class DefaultElementTests(ManifestParseTestCase):
"""Tests for <default>."""
def test_default(self):
"""Check default settings."""
a = manifest_xml._Default()
a.revisionExpr = 'foo'
a.remote = manifest_xml._XmlRemote(name='remote')
b = manifest_xml._Default()
b.revisionExpr = 'bar'
self.assertEqual(a, a)
self.assertNotEqual(a, b)
self.assertNotEqual(b, a.remote)
self.assertNotEqual(a, 123)
self.assertNotEqual(a, None)
class RemoteElementTests(ManifestParseTestCase):
"""Tests for <remote>."""
def test_remote(self):
"""Check remote settings."""
a = manifest_xml._XmlRemote(name='foo')
a.AddAnnotation('key1', 'value1', 'true')
b = manifest_xml._XmlRemote(name='foo')
b.AddAnnotation('key2', 'value1', 'true')
c = manifest_xml._XmlRemote(name='foo')
c.AddAnnotation('key1', 'value2', 'true')
d = manifest_xml._XmlRemote(name='foo')
d.AddAnnotation('key1', 'value1', 'false')
self.assertEqual(a, a)
self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(a, d)
self.assertNotEqual(a, manifest_xml._Default())
self.assertNotEqual(a, 123)
self.assertNotEqual(a, None)
class RemoveProjectElementTests(ManifestParseTestCase):
"""Tests for <remove-project>."""
def test_remove_one_project(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" />
<remove-project name="myproject" />
</manifest>
""")
self.assertEqual(manifest.projects, [])
def test_remove_one_project_one_remains(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" />
<project name="yourproject" />
<remove-project name="myproject" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].name, 'yourproject')
def test_remove_one_project_doesnt_exist(self):
with self.assertRaises(manifest_xml.ManifestParseError):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<remove-project name="myproject" />
</manifest>
""")
manifest.projects
def test_remove_one_optional_project_doesnt_exist(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<remove-project name="myproject" optional="true" />
</manifest>
""")
self.assertEqual(manifest.projects, [])
class ExtendProjectElementTests(ManifestParseTestCase):
"""Tests for <extend-project>."""
def test_extend_project_dest_path_single_match(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" />
<extend-project name="myproject" dest-path="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].relpath, 'bar')
def test_extend_project_dest_path_multi_match(self):
with self.assertRaises(manifest_xml.ManifestParseError):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" path="x" />
<project name="myproject" path="y" />
<extend-project name="myproject" dest-path="bar" />
</manifest>
""")
manifest.projects
def test_extend_project_dest_path_multi_match_path_specified(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" path="x" />
<project name="myproject" path="y" />
<extend-project name="myproject" path="x" dest-path="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 2)
if manifest.projects[0].relpath == 'y':
self.assertEqual(manifest.projects[1].relpath, 'bar')
else:
self.assertEqual(manifest.projects[0].relpath, 'bar')
self.assertEqual(manifest.projects[1].relpath, 'y')
def test_extend_project_dest_branch(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" dest-branch="foo" />
<project name="myproject" />
<extend-project name="myproject" dest-branch="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].dest_branch, 'bar')
def test_extend_project_upstream(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" />
<extend-project name="myproject" upstream="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].upstream, 'bar')

View File

@ -0,0 +1,50 @@
# Copyright 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the platform_utils.py module."""
import os
import tempfile
import unittest
import platform_utils
class RemoveTests(unittest.TestCase):
"""Check remove() helper."""
def testMissingOk(self):
"""Check missing_ok handling."""
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, 'test')
# Should not fail.
platform_utils.remove(path, missing_ok=True)
# Should fail.
self.assertRaises(OSError, platform_utils.remove, path)
self.assertRaises(OSError, platform_utils.remove, path, missing_ok=False)
# Should not fail if it exists.
open(path, 'w').close()
platform_utils.remove(path, missing_ok=True)
self.assertFalse(os.path.exists(path))
open(path, 'w').close()
platform_utils.remove(path)
self.assertFalse(os.path.exists(path))
open(path, 'w').close()
platform_utils.remove(path, missing_ok=False)
self.assertFalse(os.path.exists(path))

405
tests/test_project.py Normal file
View File

@ -0,0 +1,405 @@
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the project.py module."""
import contextlib
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
import error
import git_command
import git_config
import platform_utils
import project
@contextlib.contextmanager
def TempGitTree():
"""Create a new empty git checkout for testing."""
with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
# Tests need to assume, that main is default branch at init,
# which is not supported in config until 2.28.
cmd = ['git', 'init']
if git_command.git_require((2, 28, 0)):
cmd += ['--initial-branch=main']
else:
# Use template dir for init.
templatedir = tempfile.mkdtemp(prefix='.test-template')
with open(os.path.join(templatedir, 'HEAD'), 'w') as fp:
fp.write('ref: refs/heads/main\n')
cmd += ['--template', templatedir]
subprocess.check_call(cmd, cwd=tempdir)
yield tempdir
class FakeProject(object):
"""A fake for Project for basic functionality."""
def __init__(self, worktree):
self.worktree = worktree
self.gitdir = os.path.join(worktree, '.git')
self.name = 'fakeproject'
self.work_git = project.Project._GitGetByExec(
self, bare=False, gitdir=self.gitdir)
self.bare_git = project.Project._GitGetByExec(
self, bare=True, gitdir=self.gitdir)
self.config = git_config.GitConfig.ForRepository(gitdir=self.gitdir)
class ReviewableBranchTests(unittest.TestCase):
"""Check ReviewableBranch behavior."""
def test_smoke(self):
"""A quick run through everything."""
with TempGitTree() as tempdir:
fakeproj = FakeProject(tempdir)
# Generate some commits.
with open(os.path.join(tempdir, 'readme'), 'w') as fp:
fp.write('txt')
fakeproj.work_git.add('readme')
fakeproj.work_git.commit('-mAdd file')
fakeproj.work_git.checkout('-b', 'work')
fakeproj.work_git.rm('-f', 'readme')
fakeproj.work_git.commit('-mDel file')
# Start off with the normal details.
rb = project.ReviewableBranch(
fakeproj, fakeproj.config.GetBranch('work'), 'main')
self.assertEqual('work', rb.name)
self.assertEqual(1, len(rb.commits))
self.assertIn('Del file', rb.commits[0])
d = rb.unabbrev_commits
self.assertEqual(1, len(d))
short, long = next(iter(d.items()))
self.assertTrue(long.startswith(short))
self.assertTrue(rb.base_exists)
# Hard to assert anything useful about this.
self.assertTrue(rb.date)
# Now delete the tracking branch!
fakeproj.work_git.branch('-D', 'main')
rb = project.ReviewableBranch(
fakeproj, fakeproj.config.GetBranch('work'), 'main')
self.assertEqual(0, len(rb.commits))
self.assertFalse(rb.base_exists)
# Hard to assert anything useful about this.
self.assertTrue(rb.date)
class CopyLinkTestCase(unittest.TestCase):
"""TestCase for stub repo client checkouts.
It'll have a layout like:
tempdir/ # self.tempdir
checkout/ # self.topdir
git-project/ # self.worktree
Attributes:
tempdir: A dedicated temporary directory.
worktree: The top of the repo client checkout.
topdir: The top of a project checkout.
"""
def setUp(self):
self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
self.tempdir = self.tempdirobj.name
self.topdir = os.path.join(self.tempdir, 'checkout')
self.worktree = os.path.join(self.topdir, 'git-project')
os.makedirs(self.topdir)
os.makedirs(self.worktree)
def tearDown(self):
self.tempdirobj.cleanup()
@staticmethod
def touch(path):
with open(path, 'w'):
pass
def assertExists(self, path, msg=None):
"""Make sure |path| exists."""
if os.path.exists(path):
return
if msg is None:
msg = ['path is missing: %s' % path]
while path != '/':
path = os.path.dirname(path)
if not path:
# If we're given something like "foo", abort once we get to "".
break
result = os.path.exists(path)
msg.append('\tos.path.exists(%s): %s' % (path, result))
if result:
msg.append('\tcontents: %r' % os.listdir(path))
break
msg = '\n'.join(msg)
raise self.failureException(msg)
class CopyFile(CopyLinkTestCase):
"""Check _CopyFile handling."""
def CopyFile(self, src, dest):
return project._CopyFile(self.worktree, src, self.topdir, dest)
def test_basic(self):
"""Basic test of copying a file from a project to the toplevel."""
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
cf = self.CopyFile('foo.txt', 'foo')
cf._Copy()
self.assertExists(os.path.join(self.topdir, 'foo'))
def test_src_subdir(self):
"""Copy a file from a subdir of a project."""
src = os.path.join(self.worktree, 'bar', 'foo.txt')
os.makedirs(os.path.dirname(src))
self.touch(src)
cf = self.CopyFile('bar/foo.txt', 'new.txt')
cf._Copy()
self.assertExists(os.path.join(self.topdir, 'new.txt'))
def test_dest_subdir(self):
"""Copy a file to a subdir of a checkout."""
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
cf = self.CopyFile('foo.txt', 'sub/dir/new.txt')
self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub')))
cf._Copy()
self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'new.txt'))
def test_update(self):
"""Make sure changed files get copied again."""
src = os.path.join(self.worktree, 'foo.txt')
dest = os.path.join(self.topdir, 'bar')
with open(src, 'w') as f:
f.write('1st')
cf = self.CopyFile('foo.txt', 'bar')
cf._Copy()
self.assertExists(dest)
with open(dest) as f:
self.assertEqual(f.read(), '1st')
with open(src, 'w') as f:
f.write('2nd!')
cf._Copy()
with open(dest) as f:
self.assertEqual(f.read(), '2nd!')
def test_src_block_symlink(self):
"""Do not allow reading from a symlinked path."""
src = os.path.join(self.worktree, 'foo.txt')
sym = os.path.join(self.worktree, 'sym')
self.touch(src)
platform_utils.symlink('foo.txt', sym)
self.assertExists(sym)
cf = self.CopyFile('sym', 'foo')
self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
def test_src_block_symlink_traversal(self):
"""Do not allow reading through a symlink dir."""
realfile = os.path.join(self.tempdir, 'file.txt')
self.touch(realfile)
src = os.path.join(self.worktree, 'bar', 'file.txt')
platform_utils.symlink(self.tempdir, os.path.join(self.worktree, 'bar'))
self.assertExists(src)
cf = self.CopyFile('bar/file.txt', 'foo')
self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
def test_src_block_copy_from_dir(self):
"""Do not allow copying from a directory."""
src = os.path.join(self.worktree, 'dir')
os.makedirs(src)
cf = self.CopyFile('dir', 'foo')
self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
def test_dest_block_symlink(self):
"""Do not allow writing to a symlink."""
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
platform_utils.symlink('dest', os.path.join(self.topdir, 'sym'))
cf = self.CopyFile('foo.txt', 'sym')
self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
def test_dest_block_symlink_traversal(self):
"""Do not allow writing through a symlink dir."""
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
platform_utils.symlink(tempfile.gettempdir(),
os.path.join(self.topdir, 'sym'))
cf = self.CopyFile('foo.txt', 'sym/foo.txt')
self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
def test_src_block_copy_to_dir(self):
"""Do not allow copying to a directory."""
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
os.makedirs(os.path.join(self.topdir, 'dir'))
cf = self.CopyFile('foo.txt', 'dir')
self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
class LinkFile(CopyLinkTestCase):
"""Check _LinkFile handling."""
def LinkFile(self, src, dest):
return project._LinkFile(self.worktree, src, self.topdir, dest)
def test_basic(self):
"""Basic test of linking a file from a project into the toplevel."""
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
lf = self.LinkFile('foo.txt', 'foo')
lf._Link()
dest = os.path.join(self.topdir, 'foo')
self.assertExists(dest)
self.assertTrue(os.path.islink(dest))
self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest))
def test_src_subdir(self):
"""Link to a file in a subdir of a project."""
src = os.path.join(self.worktree, 'bar', 'foo.txt')
os.makedirs(os.path.dirname(src))
self.touch(src)
lf = self.LinkFile('bar/foo.txt', 'foo')
lf._Link()
self.assertExists(os.path.join(self.topdir, 'foo'))
def test_src_self(self):
"""Link to the project itself."""
dest = os.path.join(self.topdir, 'foo', 'bar')
lf = self.LinkFile('.', 'foo/bar')
lf._Link()
self.assertExists(dest)
self.assertEqual(os.path.join('..', 'git-project'), os.readlink(dest))
def test_dest_subdir(self):
"""Link a file to a subdir of a checkout."""
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
lf = self.LinkFile('foo.txt', 'sub/dir/foo/bar')
self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub')))
lf._Link()
self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'foo', 'bar'))
def test_src_block_relative(self):
"""Do not allow relative symlinks."""
BAD_SOURCES = (
'./',
'..',
'../',
'foo/.',
'foo/./bar',
'foo/..',
'foo/../foo',
)
for src in BAD_SOURCES:
lf = self.LinkFile(src, 'foo')
self.assertRaises(error.ManifestInvalidPathError, lf._Link)
def test_update(self):
"""Make sure changed targets get updated."""
dest = os.path.join(self.topdir, 'sym')
src = os.path.join(self.worktree, 'foo.txt')
self.touch(src)
lf = self.LinkFile('foo.txt', 'sym')
lf._Link()
self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest))
# Point the symlink somewhere else.
os.unlink(dest)
platform_utils.symlink(self.tempdir, dest)
lf._Link()
self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest))
class MigrateWorkTreeTests(unittest.TestCase):
"""Check _MigrateOldWorkTreeGitDir handling."""
_SYMLINKS = {
'config', 'description', 'hooks', 'info', 'logs', 'objects',
'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
}
_FILES = {
'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD',
'unknown-file-should-be-migrated',
}
_CLEAN_FILES = {
'a-vim-temp-file~', '#an-emacs-temp-file#',
}
@classmethod
@contextlib.contextmanager
def _simple_layout(cls):
"""Create a simple repo client checkout to test against."""
with tempfile.TemporaryDirectory() as tempdir:
tempdir = Path(tempdir)
gitdir = tempdir / '.repo/projects/src/test.git'
gitdir.mkdir(parents=True)
cmd = ['git', 'init', '--bare', str(gitdir)]
subprocess.check_call(cmd)
dotgit = tempdir / 'src/test/.git'
dotgit.mkdir(parents=True)
for name in cls._SYMLINKS:
(dotgit / name).symlink_to(f'../../../.repo/projects/src/test.git/{name}')
for name in cls._FILES | cls._CLEAN_FILES:
(dotgit / name).write_text(name)
yield tempdir
def test_standard(self):
"""Migrate a standard checkout that we expect."""
with self._simple_layout() as tempdir:
dotgit = tempdir / 'src/test/.git'
project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
# Make sure the dir was transformed into a symlink.
self.assertTrue(dotgit.is_symlink())
self.assertEqual(os.readlink(dotgit), '../../.repo/projects/src/test.git')
# Make sure files were moved over.
gitdir = tempdir / '.repo/projects/src/test.git'
for name in self._FILES:
self.assertEqual(name, (gitdir / name).read_text())
# Make sure files were removed.
for name in self._CLEAN_FILES:
self.assertFalse((gitdir / name).exists())
def test_unknown(self):
"""A checkout with unknown files should abort."""
with self._simple_layout() as tempdir:
dotgit = tempdir / 'src/test/.git'
(tempdir / '.repo/projects/src/test.git/random-file').write_text('one')
(dotgit / 'random-file').write_text('two')
with self.assertRaises(error.GitError):
project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
# Make sure no content was actually changed.
self.assertTrue(dotgit.is_dir())
for name in self._FILES:
self.assertTrue((dotgit / name).is_file())
for name in self._CLEAN_FILES:
self.assertTrue((dotgit / name).is_file())
for name in self._SYMLINKS:
self.assertTrue((dotgit / name).is_symlink())

74
tests/test_ssh.py Normal file
View File

@ -0,0 +1,74 @@
# Copyright 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the ssh.py module."""
import multiprocessing
import subprocess
import unittest
from unittest import mock
import ssh
class SshTests(unittest.TestCase):
"""Tests the ssh functions."""
def test_parse_ssh_version(self):
"""Check _parse_ssh_version() handling."""
ver = ssh._parse_ssh_version('Unknown\n')
self.assertEqual(ver, ())
ver = ssh._parse_ssh_version('OpenSSH_1.0\n')
self.assertEqual(ver, (1, 0))
ver = ssh._parse_ssh_version('OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.13, OpenSSL 1.0.1f 6 Jan 2014\n')
self.assertEqual(ver, (6, 6, 1))
ver = ssh._parse_ssh_version('OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n 7 Dec 2017\n')
self.assertEqual(ver, (7, 6))
def test_version(self):
"""Check version() handling."""
with mock.patch('ssh._run_ssh_version', return_value='OpenSSH_1.2\n'):
self.assertEqual(ssh.version(), (1, 2))
def test_context_manager_empty(self):
"""Verify context manager with no clients works correctly."""
with multiprocessing.Manager() as manager:
with ssh.ProxyManager(manager):
pass
def test_context_manager_child_cleanup(self):
"""Verify orphaned clients & masters get cleaned up."""
with multiprocessing.Manager() as manager:
with ssh.ProxyManager(manager) as ssh_proxy:
client = subprocess.Popen(['sleep', '964853320'])
ssh_proxy.add_client(client)
master = subprocess.Popen(['sleep', '964853321'])
ssh_proxy.add_master(master)
# If the process still exists, these will throw timeout errors.
client.wait(0)
master.wait(0)
def test_ssh_sock(self):
"""Check sock() function."""
manager = multiprocessing.Manager()
proxy = ssh.ProxyManager(manager)
with mock.patch('tempfile.mkdtemp', return_value='/tmp/foo'):
# old ssh version uses port
with mock.patch('ssh.version', return_value=(6, 6)):
self.assertTrue(proxy.sock().endswith('%p'))
proxy._sock_path = None
# new ssh version uses hash
with mock.patch('ssh.version', return_value=(6, 7)):
self.assertTrue(proxy.sock().endswith('%C'))

73
tests/test_subcmds.py Normal file
View File

@ -0,0 +1,73 @@
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the subcmds module (mostly __init__.py than subcommands)."""
import optparse
import unittest
import subcmds
class AllCommands(unittest.TestCase):
"""Check registered all_commands."""
def test_required_basic(self):
"""Basic checking of registered commands."""
# NB: We don't test all subcommands as we want to avoid "change detection"
# tests, so we just look for the most common/important ones here that are
# unlikely to ever change.
for cmd in {'cherry-pick', 'help', 'init', 'start', 'sync', 'upload'}:
self.assertIn(cmd, subcmds.all_commands)
def test_naming(self):
"""Verify we don't add things that we shouldn't."""
for cmd in subcmds.all_commands:
# Reject filename suffixes like "help.py".
self.assertNotIn('.', cmd)
# Make sure all '_' were converted to '-'.
self.assertNotIn('_', cmd)
# Reject internal python paths like "__init__".
self.assertFalse(cmd.startswith('__'))
def test_help_desc_style(self):
"""Force some consistency in option descriptions.
Python's optparse & argparse has a few default options like --help. Their
option description text uses lowercase sentence fragments, so enforce our
options follow the same style so UI is consistent.
We enforce:
* Text starts with lowercase.
* Text doesn't end with period.
"""
for name, cls in subcmds.all_commands.items():
cmd = cls()
parser = cmd.OptionParser
for option in parser.option_list:
if option.help == optparse.SUPPRESS_HELP:
continue
c = option.help[0]
self.assertEqual(
c.lower(), c,
msg=f'subcmds/{name}.py: {option.get_opt_string()}: help text '
f'should start with lowercase: "{option.help}"')
self.assertNotEqual(
option.help[-1], '.',
msg=f'subcmds/{name}.py: {option.get_opt_string()}: help text '
f'should not end in a period: "{option.help}"')

View File

@ -0,0 +1,49 @@
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the subcmds/init.py module."""
import unittest
from subcmds import init
class InitCommand(unittest.TestCase):
"""Check registered all_commands."""
def setUp(self):
self.cmd = init.Init()
def test_cli_parser_good(self):
"""Check valid command line options."""
ARGV = (
[],
)
for argv in ARGV:
opts, args = self.cmd.OptionParser.parse_args(argv)
self.cmd.ValidateOptions(opts, args)
def test_cli_parser_bad(self):
"""Check invalid command line options."""
ARGV = (
# Too many arguments.
['url', 'asdf'],
# Conflicting options.
['--mirror', '--archive'],
)
for argv in ARGV:
opts, args = self.cmd.OptionParser.parse_args(argv)
with self.assertRaises(SystemExit):
self.cmd.ValidateOptions(opts, args)

View File

@ -0,0 +1,45 @@
# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the subcmds/sync.py module."""
from unittest import mock
import pytest
from subcmds import sync
@pytest.mark.parametrize(
'use_superproject, cli_args, result',
[
(True, ['--current-branch'], True),
(True, ['--no-current-branch'], True),
(True, [], True),
(False, ['--current-branch'], True),
(False, ['--no-current-branch'], False),
(False, [], None),
]
)
def test_get_current_branch_only(use_superproject, cli_args, result):
"""Test Sync._GetCurrentBranchOnly logic.
Sync._GetCurrentBranchOnly should return True if a superproject is requested,
and otherwise the value of the current_branch_only option.
"""
cmd = sync.Sync()
opts, _ = cmd.OptionParser.parse_args(cli_args)
with mock.patch('git_superproject.UseSuperproject', return_value=use_superproject):
assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result

557
tests/test_wrapper.py Normal file
View File

@ -0,0 +1,557 @@
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the wrapper.py module."""
from io import StringIO
import os
import re
import sys
import tempfile
import unittest
from unittest import mock
import git_command
import main
import wrapper
def fixture(*paths):
"""Return a path relative to tests/fixtures.
"""
return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
class RepoWrapperTestCase(unittest.TestCase):
"""TestCase for the wrapper module."""
def setUp(self):
"""Load the wrapper module every time."""
wrapper._wrapper_module = None
self.wrapper = wrapper.Wrapper()
class RepoWrapperUnitTest(RepoWrapperTestCase):
"""Tests helper functions in the repo wrapper
"""
def test_version(self):
"""Make sure _Version works."""
with self.assertRaises(SystemExit) as e:
with mock.patch('sys.stdout', new_callable=StringIO) as stdout:
with mock.patch('sys.stderr', new_callable=StringIO) as stderr:
self.wrapper._Version()
self.assertEqual(0, e.exception.code)
self.assertEqual('', stderr.getvalue())
self.assertIn('repo launcher version', stdout.getvalue())
def test_python_constraints(self):
"""The launcher should never require newer than main.py."""
self.assertGreaterEqual(main.MIN_PYTHON_VERSION_HARD,
wrapper.MIN_PYTHON_VERSION_HARD)
self.assertGreaterEqual(main.MIN_PYTHON_VERSION_SOFT,
wrapper.MIN_PYTHON_VERSION_SOFT)
# Make sure the versions are themselves in sync.
self.assertGreaterEqual(wrapper.MIN_PYTHON_VERSION_SOFT,
wrapper.MIN_PYTHON_VERSION_HARD)
def test_init_parser(self):
"""Make sure 'init' GetParser works."""
parser = self.wrapper.GetParser(gitc_init=False)
opts, args = parser.parse_args([])
self.assertEqual([], args)
self.assertIsNone(opts.manifest_url)
def test_gitc_init_parser(self):
"""Make sure 'gitc-init' GetParser works."""
parser = self.wrapper.GetParser(gitc_init=True)
opts, args = parser.parse_args([])
self.assertEqual([], args)
self.assertIsNone(opts.manifest_file)
def test_get_gitc_manifest_dir_no_gitc(self):
"""
Test reading a missing gitc config file
"""
self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config')
val = self.wrapper.get_gitc_manifest_dir()
self.assertEqual(val, '')
def test_get_gitc_manifest_dir(self):
"""
Test reading the gitc config file and parsing the directory
"""
self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config')
val = self.wrapper.get_gitc_manifest_dir()
self.assertEqual(val, '/test/usr/local/google/gitc')
def test_gitc_parse_clientdir_no_gitc(self):
"""
Test parsing the gitc clientdir without gitc running
"""
self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None)
self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test')
def test_gitc_parse_clientdir(self):
"""
Test parsing the gitc clientdir
"""
self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None)
self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/'), 'test')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/extra'), 'test')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test'), 'test')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/'), 'test')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/extra'),
'test')
self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/'), None)
self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/'), None)
class SetGitTrace2ParentSid(RepoWrapperTestCase):
"""Check SetGitTrace2ParentSid behavior."""
KEY = 'GIT_TRACE2_PARENT_SID'
VALID_FORMAT = re.compile(r'^repo-[0-9]{8}T[0-9]{6}Z-P[0-9a-f]{8}$')
def test_first_set(self):
"""Test env var not yet set."""
env = {}
self.wrapper.SetGitTrace2ParentSid(env)
self.assertIn(self.KEY, env)
value = env[self.KEY]
self.assertRegex(value, self.VALID_FORMAT)
def test_append(self):
"""Test env var is appended."""
env = {self.KEY: 'pfx'}
self.wrapper.SetGitTrace2ParentSid(env)
self.assertIn(self.KEY, env)
value = env[self.KEY]
self.assertTrue(value.startswith('pfx/'))
self.assertRegex(value[4:], self.VALID_FORMAT)
def test_global_context(self):
"""Check os.environ gets updated by default."""
os.environ.pop(self.KEY, None)
self.wrapper.SetGitTrace2ParentSid()
self.assertIn(self.KEY, os.environ)
value = os.environ[self.KEY]
self.assertRegex(value, self.VALID_FORMAT)
class RunCommand(RepoWrapperTestCase):
"""Check run_command behavior."""
def test_capture(self):
"""Check capture_output handling."""
ret = self.wrapper.run_command(['echo', 'hi'], capture_output=True)
self.assertEqual(ret.stdout, 'hi\n')
def test_check(self):
"""Check check handling."""
self.wrapper.run_command(['true'], check=False)
self.wrapper.run_command(['true'], check=True)
self.wrapper.run_command(['false'], check=False)
with self.assertRaises(self.wrapper.RunError):
self.wrapper.run_command(['false'], check=True)
class RunGit(RepoWrapperTestCase):
"""Check run_git behavior."""
def test_capture(self):
"""Check capture_output handling."""
ret = self.wrapper.run_git('--version')
self.assertIn('git', ret.stdout)
def test_check(self):
"""Check check handling."""
with self.assertRaises(self.wrapper.CloneFailure):
self.wrapper.run_git('--version-asdfasdf')
self.wrapper.run_git('--version-asdfasdf', check=False)
class ParseGitVersion(RepoWrapperTestCase):
"""Check ParseGitVersion behavior."""
def test_autoload(self):
"""Check we can load the version from the live git."""
ret = self.wrapper.ParseGitVersion()
self.assertIsNotNone(ret)
def test_bad_ver(self):
"""Check handling of bad git versions."""
ret = self.wrapper.ParseGitVersion(ver_str='asdf')
self.assertIsNone(ret)
def test_normal_ver(self):
"""Check handling of normal git versions."""
ret = self.wrapper.ParseGitVersion(ver_str='git version 2.25.1')
self.assertEqual(2, ret.major)
self.assertEqual(25, ret.minor)
self.assertEqual(1, ret.micro)
self.assertEqual('2.25.1', ret.full)
def test_extended_ver(self):
"""Check handling of extended distro git versions."""
ret = self.wrapper.ParseGitVersion(
ver_str='git version 1.30.50.696.g5e7596f4ac-goog')
self.assertEqual(1, ret.major)
self.assertEqual(30, ret.minor)
self.assertEqual(50, ret.micro)
self.assertEqual('1.30.50.696.g5e7596f4ac-goog', ret.full)
class CheckGitVersion(RepoWrapperTestCase):
"""Check _CheckGitVersion behavior."""
def test_unknown(self):
"""Unknown versions should abort."""
with mock.patch.object(self.wrapper, 'ParseGitVersion', return_value=None):
with self.assertRaises(self.wrapper.CloneFailure):
self.wrapper._CheckGitVersion()
def test_old(self):
"""Old versions should abort."""
with mock.patch.object(
self.wrapper, 'ParseGitVersion',
return_value=self.wrapper.GitVersion(1, 0, 0, '1.0.0')):
with self.assertRaises(self.wrapper.CloneFailure):
self.wrapper._CheckGitVersion()
def test_new(self):
"""Newer versions should run fine."""
with mock.patch.object(
self.wrapper, 'ParseGitVersion',
return_value=self.wrapper.GitVersion(100, 0, 0, '100.0.0')):
self.wrapper._CheckGitVersion()
class Requirements(RepoWrapperTestCase):
"""Check Requirements handling."""
def test_missing_file(self):
"""Don't crash if the file is missing (old version)."""
testdir = os.path.dirname(os.path.realpath(__file__))
self.assertIsNone(self.wrapper.Requirements.from_dir(testdir))
self.assertIsNone(self.wrapper.Requirements.from_file(
os.path.join(testdir, 'xxxxxxxxxxxxxxxxxxxxxxxx')))
def test_corrupt_data(self):
"""If the file can't be parsed, don't blow up."""
self.assertIsNone(self.wrapper.Requirements.from_file(__file__))
self.assertIsNone(self.wrapper.Requirements.from_data(b'x'))
def test_valid_data(self):
"""Make sure we can parse the file we ship."""
self.assertIsNotNone(self.wrapper.Requirements.from_data(b'{}'))
rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
self.assertIsNotNone(self.wrapper.Requirements.from_dir(rootdir))
self.assertIsNotNone(self.wrapper.Requirements.from_file(os.path.join(
rootdir, 'requirements.json')))
def test_format_ver(self):
"""Check format_ver can format."""
self.assertEqual('1.2.3', self.wrapper.Requirements._format_ver((1, 2, 3)))
self.assertEqual('1', self.wrapper.Requirements._format_ver([1]))
def test_assert_all_unknown(self):
"""Check assert_all works with incompatible file."""
reqs = self.wrapper.Requirements({})
reqs.assert_all()
def test_assert_all_new_repo(self):
"""Check assert_all accepts new enough repo."""
reqs = self.wrapper.Requirements({'repo': {'hard': [1, 0]}})
reqs.assert_all()
def test_assert_all_old_repo(self):
"""Check assert_all rejects old repo."""
reqs = self.wrapper.Requirements({'repo': {'hard': [99999, 0]}})
with self.assertRaises(SystemExit):
reqs.assert_all()
def test_assert_all_new_python(self):
"""Check assert_all accepts new enough python."""
reqs = self.wrapper.Requirements({'python': {'hard': sys.version_info}})
reqs.assert_all()
def test_assert_all_old_python(self):
"""Check assert_all rejects old python."""
reqs = self.wrapper.Requirements({'python': {'hard': [99999, 0]}})
with self.assertRaises(SystemExit):
reqs.assert_all()
def test_assert_ver_unknown(self):
"""Check assert_ver works with incompatible file."""
reqs = self.wrapper.Requirements({})
reqs.assert_ver('xxx', (1, 0))
def test_assert_ver_new(self):
"""Check assert_ver allows new enough versions."""
reqs = self.wrapper.Requirements({'git': {'hard': [1, 0], 'soft': [2, 0]}})
reqs.assert_ver('git', (1, 0))
reqs.assert_ver('git', (1, 5))
reqs.assert_ver('git', (2, 0))
reqs.assert_ver('git', (2, 5))
def test_assert_ver_old(self):
"""Check assert_ver rejects old versions."""
reqs = self.wrapper.Requirements({'git': {'hard': [1, 0], 'soft': [2, 0]}})
with self.assertRaises(SystemExit):
reqs.assert_ver('git', (0, 5))
class NeedSetupGnuPG(RepoWrapperTestCase):
"""Check NeedSetupGnuPG behavior."""
def test_missing_dir(self):
"""The ~/.repoconfig tree doesn't exist yet."""
with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = os.path.join(tempdir, 'foo')
self.assertTrue(self.wrapper.NeedSetupGnuPG())
def test_missing_keyring(self):
"""The keyring-version file doesn't exist yet."""
with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir
self.assertTrue(self.wrapper.NeedSetupGnuPG())
def test_empty_keyring(self):
"""The keyring-version file exists, but is empty."""
with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir
with open(os.path.join(tempdir, 'keyring-version'), 'w'):
pass
self.assertTrue(self.wrapper.NeedSetupGnuPG())
def test_old_keyring(self):
"""The keyring-version file exists, but it's old."""
with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir
with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp:
fp.write('1.0\n')
self.assertTrue(self.wrapper.NeedSetupGnuPG())
def test_new_keyring(self):
"""The keyring-version file exists, and is up-to-date."""
with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir
with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp:
fp.write('1000.0\n')
self.assertFalse(self.wrapper.NeedSetupGnuPG())
class SetupGnuPG(RepoWrapperTestCase):
"""Check SetupGnuPG behavior."""
def test_full(self):
"""Make sure it works completely."""
with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir
self.wrapper.gpg_dir = os.path.join(self.wrapper.home_dot_repo, 'gnupg')
self.assertTrue(self.wrapper.SetupGnuPG(True))
with open(os.path.join(tempdir, 'keyring-version'), 'r') as fp:
data = fp.read()
self.assertEqual('.'.join(str(x) for x in self.wrapper.KEYRING_VERSION),
data.strip())
class VerifyRev(RepoWrapperTestCase):
"""Check verify_rev behavior."""
def test_verify_passes(self):
"""Check when we have a valid signed tag."""
desc_result = self.wrapper.RunResult(0, 'v1.0\n', '')
gpg_result = self.wrapper.RunResult(0, '', '')
with mock.patch.object(self.wrapper, 'run_git',
side_effect=(desc_result, gpg_result)):
ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True)
self.assertEqual('v1.0^0', ret)
def test_unsigned_commit(self):
"""Check we fall back to signed tag when we have an unsigned commit."""
desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '')
gpg_result = self.wrapper.RunResult(0, '', '')
with mock.patch.object(self.wrapper, 'run_git',
side_effect=(desc_result, gpg_result)):
ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True)
self.assertEqual('v1.0^0', ret)
def test_verify_fails(self):
"""Check we fall back to signed tag when we have an unsigned commit."""
desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '')
gpg_result = Exception
with mock.patch.object(self.wrapper, 'run_git',
side_effect=(desc_result, gpg_result)):
with self.assertRaises(Exception):
self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True)
class GitCheckoutTestCase(RepoWrapperTestCase):
"""Tests that use a real/small git checkout."""
GIT_DIR = None
REV_LIST = None
@classmethod
def setUpClass(cls):
# Create a repo to operate on, but do it once per-class.
cls.tempdirobj = tempfile.TemporaryDirectory(prefix='repo-rev-tests')
cls.GIT_DIR = cls.tempdirobj.name
run_git = wrapper.Wrapper().run_git
remote = os.path.join(cls.GIT_DIR, 'remote')
os.mkdir(remote)
# Tests need to assume, that main is default branch at init,
# which is not supported in config until 2.28.
if git_command.git_require((2, 28, 0)):
initstr = '--initial-branch=main'
else:
# Use template dir for init.
templatedir = tempfile.mkdtemp(prefix='.test-template')
with open(os.path.join(templatedir, 'HEAD'), 'w') as fp:
fp.write('ref: refs/heads/main\n')
initstr = '--template=' + templatedir
run_git('init', initstr, cwd=remote)
run_git('commit', '--allow-empty', '-minit', cwd=remote)
run_git('branch', 'stable', cwd=remote)
run_git('tag', 'v1.0', cwd=remote)
run_git('commit', '--allow-empty', '-m2nd commit', cwd=remote)
cls.REV_LIST = run_git('rev-list', 'HEAD', cwd=remote).stdout.splitlines()
run_git('init', cwd=cls.GIT_DIR)
run_git('fetch', remote, '+refs/heads/*:refs/remotes/origin/*', cwd=cls.GIT_DIR)
@classmethod
def tearDownClass(cls):
if not cls.tempdirobj:
return
cls.tempdirobj.cleanup()
class ResolveRepoRev(GitCheckoutTestCase):
"""Check resolve_repo_rev behavior."""
def test_explicit_branch(self):
"""Check refs/heads/branch argument."""
rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/stable')
self.assertEqual('refs/heads/stable', rrev)
self.assertEqual(self.REV_LIST[1], lrev)
with self.assertRaises(wrapper.CloneFailure):
self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/unknown')
def test_explicit_tag(self):
"""Check refs/tags/tag argument."""
rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/tags/v1.0')
self.assertEqual('refs/tags/v1.0', rrev)
self.assertEqual(self.REV_LIST[1], lrev)
with self.assertRaises(wrapper.CloneFailure):
self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/tags/unknown')
def test_branch_name(self):
"""Check branch argument."""
rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'stable')
self.assertEqual('refs/heads/stable', rrev)
self.assertEqual(self.REV_LIST[1], lrev)
rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'main')
self.assertEqual('refs/heads/main', rrev)
self.assertEqual(self.REV_LIST[0], lrev)
def test_tag_name(self):
"""Check tag argument."""
rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'v1.0')
self.assertEqual('refs/tags/v1.0', rrev)
self.assertEqual(self.REV_LIST[1], lrev)
def test_full_commit(self):
"""Check specific commit argument."""
commit = self.REV_LIST[0]
rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit)
self.assertEqual(commit, rrev)
self.assertEqual(commit, lrev)
def test_partial_commit(self):
"""Check specific (partial) commit argument."""
commit = self.REV_LIST[0][0:20]
rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit)
self.assertEqual(self.REV_LIST[0], rrev)
self.assertEqual(self.REV_LIST[0], lrev)
def test_unknown(self):
"""Check unknown ref/commit argument."""
with self.assertRaises(wrapper.CloneFailure):
self.wrapper.resolve_repo_rev(self.GIT_DIR, 'boooooooya')
class CheckRepoVerify(RepoWrapperTestCase):
"""Check check_repo_verify behavior."""
def test_no_verify(self):
"""Always fail with --no-repo-verify."""
self.assertFalse(self.wrapper.check_repo_verify(False))
def test_gpg_initialized(self):
"""Should pass if gpg is setup already."""
with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=False):
self.assertTrue(self.wrapper.check_repo_verify(True))
def test_need_gpg_setup(self):
"""Should pass/fail based on gpg setup."""
with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=True):
with mock.patch.object(self.wrapper, 'SetupGnuPG') as m:
m.return_value = True
self.assertTrue(self.wrapper.check_repo_verify(True))
m.return_value = False
self.assertFalse(self.wrapper.check_repo_verify(True))
class CheckRepoRev(GitCheckoutTestCase):
"""Check check_repo_rev behavior."""
def test_verify_works(self):
"""Should pass when verification passes."""
with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True):
with mock.patch.object(self.wrapper, 'verify_rev', return_value='12345'):
rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable')
self.assertEqual('refs/heads/stable', rrev)
self.assertEqual('12345', lrev)
def test_verify_fails(self):
"""Should fail when verification fails."""
with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True):
with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception):
with self.assertRaises(Exception):
self.wrapper.check_repo_rev(self.GIT_DIR, 'stable')
def test_verify_ignore(self):
"""Should pass when verification is disabled."""
with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception):
rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable', repo_verify=False)
self.assertEqual('refs/heads/stable', rrev)
self.assertEqual(self.REV_LIST[1], lrev)
if __name__ == '__main__':
unittest.main()

View File

@ -1,5 +1,4 @@
#
# Copyright (C) 2013 The Android Open Source Project
# Copyright 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -13,7 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
# https://tox.readthedocs.io/
def is_python3():
return sys.version_info[0] == 3
[tox]
envlist = py36, py37, py38, py39
[gh-actions]
python =
3.6: py36
3.7: py37
3.8: py38
3.9: py39
[testenv]
deps = pytest
commands = {envpython} run_tests
setenv =
GIT_AUTHOR_NAME = Repo test author
GIT_COMMITTER_NAME = Repo test committer
EMAIL = repo@gerrit.nodomain

View File

@ -1,5 +1,3 @@
#!/usr/bin/env python
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -14,17 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import imp
try:
from importlib.machinery import SourceFileLoader
_loader = lambda *args: SourceFileLoader(*args).load_module()
except ImportError:
import imp
_loader = lambda *args: imp.load_source(*args)
import os
def WrapperPath():
return os.path.join(os.path.dirname(__file__), 'repo')
_wrapper_module = None
def Wrapper():
global _wrapper_module
if not _wrapper_module:
_wrapper_module = imp.load_source('wrapper', WrapperPath())
_wrapper_module = _loader('wrapper', WrapperPath())
return _wrapper_module