Compare commits

...

38 Commits
v2.24 ... v2.29

Author SHA1 Message Date
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
51 changed files with 1309 additions and 407 deletions

View File

@ -144,11 +144,10 @@ class Command(object):
help=f'number of jobs to run in parallel (default: {default})') help=f'number of jobs to run in parallel (default: {default})')
m = p.add_option_group('Multi-manifest options') m = p.add_option_group('Multi-manifest options')
m.add_option('--outer-manifest', action='store_true', m.add_option('--outer-manifest', action='store_true', default=None,
help='operate starting at the outermost manifest') help='operate starting at the outermost manifest')
m.add_option('--no-outer-manifest', dest='outer_manifest', m.add_option('--no-outer-manifest', dest='outer_manifest',
action='store_false', default=None, action='store_false', help='do not operate on outer manifests')
help='do not operate on outer manifests')
m.add_option('--this-manifest-only', action='store_true', default=None, m.add_option('--this-manifest-only', action='store_true', default=None,
help='only operate on this (sub)manifest') help='only operate on this (sub)manifest')
m.add_option('--no-this-manifest-only', '--all-manifests', m.add_option('--no-this-manifest-only', '--all-manifests',
@ -186,6 +185,10 @@ class Command(object):
"""Validate common options.""" """Validate common options."""
opt.quiet = opt.output_mode is False opt.quiet = opt.output_mode is False
opt.verbose = opt.output_mode is True 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): def ValidateOptions(self, opt, args):
"""Validate the user options & arguments before executing. """Validate the user options & arguments before executing.
@ -274,6 +277,18 @@ class Command(object):
def GetProjects(self, args, manifest=None, groups='', missing_ok=False, def GetProjects(self, args, manifest=None, groups='', missing_ok=False,
submodules_ok=False, all_manifests=False): submodules_ok=False, all_manifests=False):
"""A list of projects that match the arguments. """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.
""" """
if all_manifests: if all_manifests:
if not manifest: if not manifest:
@ -385,7 +400,7 @@ class Command(object):
opt: The command options. opt: The command options.
""" """
top = self.outer_manifest top = self.outer_manifest
if opt.outer_manifest is False or opt.this_manifest_only: if not opt.outer_manifest or opt.this_manifest_only:
top = self.manifest top = self.manifest
yield top yield top
if not opt.this_manifest_only: if not opt.this_manifest_only:

View File

@ -66,6 +66,7 @@ following DTD:
<!ATTLIST submanifest revision CDATA #IMPLIED> <!ATTLIST submanifest revision CDATA #IMPLIED>
<!ATTLIST submanifest path CDATA #IMPLIED> <!ATTLIST submanifest path CDATA #IMPLIED>
<!ATTLIST submanifest groups CDATA #IMPLIED> <!ATTLIST submanifest groups CDATA #IMPLIED>
<!ATTLIST submanifest default-groups CDATA #IMPLIED>
<!ELEMENT project (annotation*, <!ELEMENT project (annotation*,
project*, project*,
@ -302,6 +303,9 @@ in the included submanifest belong. This appends and recurses, meaning
all projects in submanifests carry all parent submanifest groups. all projects in submanifests carry all parent submanifest groups.
Same syntax as the corresponding element of `project`. 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 ### Element project

View File

@ -158,6 +158,8 @@ def git_require(min_version, fail=False, msg=''):
class GitCommand(object): class GitCommand(object):
"""Wrapper around a single git invocation."""
def __init__(self, def __init__(self,
project, project,
cmdv, cmdv,
@ -279,14 +281,9 @@ class GitCommand(object):
ssh_proxy.add_client(p) ssh_proxy.add_client(p)
self.process = p self.process = p
if input:
if isinstance(input, str):
input = input.encode('utf-8')
p.stdin.write(input)
p.stdin.close()
try: try:
self.stdout, self.stderr = p.communicate() self.stdout, self.stderr = p.communicate(input=input)
finally: finally:
if ssh_proxy: if ssh_proxy:
ssh_proxy.remove_client(p) ssh_proxy.remove_client(p)

View File

@ -18,7 +18,7 @@ For more information on superproject, check out:
https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
Examples: Examples:
superproject = Superproject() superproject = Superproject(manifest, name, remote, revision)
UpdateProjectsResult = superproject.UpdateProjectsRevisionId(projects) UpdateProjectsResult = superproject.UpdateProjectsRevisionId(projects)
""" """
@ -99,8 +99,8 @@ class Superproject(object):
self._work_git_name = git_name + _SUPERPROJECT_GIT_NAME self._work_git_name = git_name + _SUPERPROJECT_GIT_NAME
self._work_git = os.path.join(self._superproject_path, self._work_git_name) self._work_git = os.path.join(self._superproject_path, self._work_git_name)
# The following are command arguemnts, rather then superproject attributes, # The following are command arguemnts, rather than superproject attributes,
# and where included here originally. They should eventually become # and were included here originally. They should eventually become
# arguments that are passed down from the public methods, instead of being # arguments that are passed down from the public methods, instead of being
# treated as attributes. # treated as attributes.
self._git_event_log = None self._git_event_log = None
@ -295,7 +295,8 @@ class Superproject(object):
if not os.path.exists(self._superproject_path): if not os.path.exists(self._superproject_path):
self._LogWarning(f'missing superproject directory: {self._superproject_path}') self._LogWarning(f'missing superproject directory: {self._superproject_path}')
return None return None
manifest_str = self._manifest.ToXml(groups=self._manifest.GetGroupsStr()).toxml() manifest_str = self._manifest.ToXml(groups=self._manifest.GetGroupsStr(),
omit_local=True).toxml()
manifest_path = self._manifest_path manifest_path = self._manifest_path
try: try:
with open(manifest_path, 'w', encoding='utf-8') as fp: with open(manifest_path, 'w', encoding='utf-8') as fp:
@ -329,7 +330,8 @@ class Superproject(object):
"""Update revisionId of every project in projects with the commit id. """Update revisionId of every project in projects with the commit id.
Args: Args:
projects: List of projects whose revisionId needs to be updated. projects: a list of projects whose revisionId needs to be updated.
git_event_log: an EventLog, for git tracing.
Returns: Returns:
UpdateProjectsResult UpdateProjectsResult
@ -431,9 +433,15 @@ def UseSuperproject(use_superproject, manifest):
Args: Args:
use_superproject: option value from optparse. use_superproject: option value from optparse.
manifest: manifest to use. manifest: manifest to use.
Returns:
Whether the superproject should be used.
""" """
if use_superproject is not None: if not manifest.superproject:
# This (sub) manifest does not have a superproject definition.
return False
elif use_superproject is not None:
return use_superproject return use_superproject
else: else:
client_value = manifest.manifestProject.use_superproject client_value = manifest.manifestProject.use_superproject

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
# From Gerrit Code Review 3.1.3 # From Gerrit Code Review 3.6.1 c67916dbdc07555c44e32a68f92ffc484b9b34f0
# #
# Part of Gerrit Code Review (https://www.gerritcodereview.com/) # Part of Gerrit Code Review (https://www.gerritcodereview.com/)
# #
@ -17,6 +17,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
set -u
# avoid [[ which is not POSIX sh. # avoid [[ which is not POSIX sh.
if test "$#" != 1 ; then if test "$#" != 1 ; then
echo "$0 requires an argument." echo "$0 requires an argument."
@ -29,15 +31,25 @@ if test ! -f "$1" ; then
fi fi
# Do not create a change id if requested # Do not create a change id if requested
if test "false" = "`git config --bool --get gerrit.createChangeId`" ; then if test "false" = "$(git config --bool --get gerrit.createChangeId)" ; then
exit 0 exit 0
fi fi
# $RANDOM will be undefined if not using bash, so don't use set -u # Do not create a change id for squash commits.
random=$( (whoami ; hostname ; date; cat $1 ; echo $RANDOM) | git hash-object --stdin) if head -n1 "$1" | grep -q '^squash! '; then
exit 0
fi
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
random=$({ git var GIT_COMMITTER_IDENT ; echo "$refhash" ; cat "$1"; } | git hash-object --stdin)
dest="$1.tmp.${random}" dest="$1.tmp.${random}"
trap 'rm -f "${dest}"' EXIT trap 'rm -f "$dest" "$dest-2"' EXIT
if ! git stripspace --strip-comments < "$1" > "${dest}" ; then if ! git stripspace --strip-comments < "$1" > "${dest}" ; then
echo "cannot strip comments from $1" echo "cannot strip comments from $1"
@ -49,11 +61,40 @@ if test ! -s "${dest}" ; then
exit 1 exit 1
fi fi
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
if git interpret-trailers --parse < "$1" | grep -q "^$token: $pattern$" ; then
exit 0
fi
# 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 # Avoid the --in-place option which only appeared in Git 2.8
# Avoid the --if-exists option which only appeared in Git 2.15 if ! git interpret-trailers \
if ! git -c trailer.ifexists=doNothing interpret-trailers \ --trailer "Signed-off-by: SENTINEL" < "$1" > "$dest-2" ; then
--trailer "Change-Id: I${random}" < "$1" > "${dest}" ; then echo "cannot insert Signed-off-by sentinel line in $1"
echo "cannot insert change-id line in $1" exit 1
fi
# 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 exit 1
fi fi

View File

@ -294,8 +294,7 @@ class _Repo(object):
cmd.ValidateOptions(copts, cargs) cmd.ValidateOptions(copts, cargs)
this_manifest_only = copts.this_manifest_only this_manifest_only = copts.this_manifest_only
# If not specified, default to using the outer manifest. outer_manifest = copts.outer_manifest
outer_manifest = copts.outer_manifest is not False
if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only: if cmd.MULTI_MANIFEST_SUPPORT or this_manifest_only:
result = cmd.Execute(copts, cargs) result = cmd.Execute(copts, cargs)
elif outer_manifest and repo_client.manifest.is_submanifest: elif outer_manifest and repo_client.manifest.is_submanifest:

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo abandon" "Repo Manual" .TH REPO "1" "July 2022" "repo abandon" "Repo Manual"
.SH NAME .SH NAME
repo \- repo abandon - manual page for repo abandon repo \- repo abandon - manual page for repo abandon
.SH SYNOPSIS .SH SYNOPSIS
@ -32,5 +32,18 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help abandon` to view the detailed manual. Run `repo help abandon` to view the detailed manual.

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo branches" "Repo Manual" .TH REPO "1" "July 2022" "repo branches" "Repo Manual"
.SH NAME .SH NAME
repo \- repo branches - manual page for repo branches repo \- repo branches - manual page for repo branches
.SH SYNOPSIS .SH SYNOPSIS
@ -55,5 +55,18 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help branches` to view the detailed manual. Run `repo help branches` to view the detailed manual.

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo checkout" "Repo Manual" .TH REPO "1" "July 2022" "repo checkout" "Repo Manual"
.SH NAME .SH NAME
repo \- repo checkout - manual page for repo checkout repo \- repo checkout - manual page for repo checkout
.SH SYNOPSIS .SH SYNOPSIS
@ -24,6 +24,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help checkout` to view the detailed manual. Run `repo help checkout` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo cherry-pick" "Repo Manual" .TH REPO "1" "July 2022" "repo cherry-pick" "Repo Manual"
.SH NAME .SH NAME
repo \- repo cherry-pick - manual page for repo cherry-pick repo \- repo cherry-pick - manual page for repo cherry-pick
.SH SYNOPSIS .SH SYNOPSIS
@ -20,6 +20,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help cherry\-pick` to view the detailed manual. Run `repo help cherry\-pick` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo diff" "Repo Manual" .TH REPO "1" "July 2022" "repo diff" "Repo Manual"
.SH NAME .SH NAME
repo \- repo diff - manual page for repo diff repo \- repo diff - manual page for repo diff
.SH SYNOPSIS .SH SYNOPSIS
@ -31,5 +31,18 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help diff` to view the detailed manual. Run `repo help diff` to view the detailed manual.

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo diffmanifests" "Repo Manual" .TH REPO "1" "July 2022" "repo diffmanifests" "Repo Manual"
.SH NAME .SH NAME
repo \- repo diffmanifests - manual page for repo diffmanifests repo \- repo diffmanifests - manual page for repo diffmanifests
.SH SYNOPSIS .SH SYNOPSIS
@ -29,6 +29,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help diffmanifests` to view the detailed manual. Run `repo help diffmanifests` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo download" "Repo Manual" .TH REPO "1" "July 2022" "repo download" "Repo Manual"
.SH NAME .SH NAME
repo \- repo download - manual page for repo download repo \- repo download - manual page for repo download
.SH SYNOPSIS .SH SYNOPSIS
@ -35,6 +35,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help download` to view the detailed manual. Run `repo help download` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo forall" "Repo Manual" .TH REPO "1" "July 2022" "repo forall" "Repo Manual"
.SH NAME .SH NAME
repo \- repo forall - manual page for repo forall repo \- repo forall - manual page for repo forall
.SH SYNOPSIS .SH SYNOPSIS
@ -54,6 +54,19 @@ only show errors
.TP .TP
\fB\-p\fR \fB\-p\fR
show project headers before output 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 .PP
Run `repo help forall` to view the detailed manual. Run `repo help forall` to view the detailed manual.
.SH DETAILS .SH DETAILS
@ -93,6 +106,11 @@ REPO_PROJECT is set to the unique name of the project.
.PP .PP
REPO_PATH is the path relative the the root of the client. REPO_PATH is the path relative the the root of the client.
.PP .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. REPO_REMOTE is the name of the remote system from the manifest.
.PP .PP
REPO_LREV is the name of the revision from the manifest, translated to a local REPO_LREV is the name of the revision from the manifest, translated to a local

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo gitc-delete" "Repo Manual" .TH REPO "1" "July 2022" "repo gitc-delete" "Repo Manual"
.SH NAME .SH NAME
repo \- repo gitc-delete - manual page for repo gitc-delete repo \- repo gitc-delete - manual page for repo gitc-delete
.SH SYNOPSIS .SH SYNOPSIS
@ -23,6 +23,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help gitc\-delete` to view the detailed manual. Run `repo help gitc\-delete` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "November 2021" "repo gitc-init" "Repo Manual" .TH REPO "1" "August 2022" "repo gitc-init" "Repo Manual"
.SH NAME .SH NAME
repo \- repo gitc-init - manual page for repo gitc-init repo \- repo gitc-init - manual page for repo gitc-init
.SH SYNOPSIS .SH SYNOPSIS
@ -45,10 +45,15 @@ sync any submodules associated with the manifest repo
\fB\-\-standalone\-manifest\fR \fB\-\-standalone\-manifest\fR
download the manifest as a static file rather then download the manifest as a static file rather then
create a git checkout of the manifest repo 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: .SS Manifest (only) checkout options:
.TP .TP
\fB\-\-current\-branch\fR \fB\-\-current\-branch\fR
fetch only current manifest branch from server fetch only current manifest branch from server
(default)
.TP .TP
\fB\-\-no\-current\-branch\fR \fB\-\-no\-current\-branch\fR
fetch all manifest branches from server fetch all manifest branches from server
@ -109,6 +114,12 @@ not \fB\-\-partial\-clone\fR)
\fB\-\-no\-clone\-bundle\fR \fB\-\-no\-clone\-bundle\fR
disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
\fB\-\-partial\-clone\fR) \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: .SS repo Version options:
.TP .TP
\fB\-\-repo\-url\fR=\fI\,URL\/\fR \fB\-\-repo\-url\fR=\fI\,URL\/\fR
@ -130,6 +141,19 @@ Optional manifest file to use for this GITC client.
.TP .TP
\fB\-c\fR GITC_CLIENT, \fB\-\-gitc\-client\fR=\fI\,GITC_CLIENT\/\fR \fB\-c\fR GITC_CLIENT, \fB\-\-gitc\-client\fR=\fI\,GITC_CLIENT\/\fR
Name of the gitc_client instance to create or modify. 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 .PP
Run `repo help gitc\-init` to view the detailed manual. Run `repo help gitc\-init` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo grep" "Repo Manual" .TH REPO "1" "July 2022" "repo grep" "Repo Manual"
.SH NAME .SH NAME
repo \- repo grep - manual page for repo grep repo \- repo grep - manual page for repo grep
.SH SYNOPSIS .SH SYNOPSIS
@ -24,6 +24,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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: .SS Sources:
.TP .TP
\fB\-\-cached\fR \fB\-\-cached\fR

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo help" "Repo Manual" .TH REPO "1" "July 2022" "repo help" "Repo Manual"
.SH NAME .SH NAME
repo \- repo help - manual page for repo help repo \- repo help - manual page for repo help
.SH SYNOPSIS .SH SYNOPSIS
@ -26,6 +26,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help help` to view the detailed manual. Run `repo help help` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo info" "Repo Manual" .TH REPO "1" "July 2022" "repo info" "Repo Manual"
.SH NAME .SH NAME
repo \- repo info - manual page for repo info repo \- repo info - manual page for repo info
.SH SYNOPSIS .SH SYNOPSIS
@ -36,5 +36,18 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help info` to view the detailed manual. Run `repo help info` to view the detailed manual.

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "November 2021" "repo init" "Repo Manual" .TH REPO "1" "August 2022" "repo init" "Repo Manual"
.SH NAME .SH NAME
repo \- repo init - manual page for repo init repo \- repo init - manual page for repo init
.SH SYNOPSIS .SH SYNOPSIS
@ -45,10 +45,15 @@ sync any submodules associated with the manifest repo
\fB\-\-standalone\-manifest\fR \fB\-\-standalone\-manifest\fR
download the manifest as a static file rather then download the manifest as a static file rather then
create a git checkout of the manifest repo 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: .SS Manifest (only) checkout options:
.TP .TP
\fB\-c\fR, \fB\-\-current\-branch\fR \fB\-c\fR, \fB\-\-current\-branch\fR
fetch only current manifest branch from server fetch only current manifest branch from server
(default)
.TP .TP
\fB\-\-no\-current\-branch\fR \fB\-\-no\-current\-branch\fR
fetch all manifest branches from server fetch all manifest branches from server
@ -109,6 +114,12 @@ not \fB\-\-partial\-clone\fR)
\fB\-\-no\-clone\-bundle\fR \fB\-\-no\-clone\-bundle\fR
disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
\fB\-\-partial\-clone\fR) \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: .SS repo Version options:
.TP .TP
\fB\-\-repo\-url\fR=\fI\,URL\/\fR \fB\-\-repo\-url\fR=\fI\,URL\/\fR
@ -123,6 +134,19 @@ do not verify repo source code
.TP .TP
\fB\-\-config\-name\fR \fB\-\-config\-name\fR
Always prompt for name/e\-mail 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 .PP
Run `repo help init` to view the detailed manual. Run `repo help init` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo list" "Repo Manual" .TH REPO "1" "July 2022" "repo list" "Repo Manual"
.SH NAME .SH NAME
repo \- repo list - manual page for repo list repo \- repo list - manual page for repo list
.SH SYNOPSIS .SH SYNOPSIS
@ -47,6 +47,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help list` to view the detailed manual. Run `repo help list` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "November 2021" "repo manifest" "Repo Manual" .TH REPO "1" "July 2022" "repo manifest" "Repo Manual"
.SH NAME .SH NAME
repo \- repo manifest - manual page for repo manifest repo \- repo manifest - manual page for repo manifest
.SH SYNOPSIS .SH SYNOPSIS
@ -40,7 +40,8 @@ format output for humans to read
ignore local manifests ignore local manifests
.TP .TP
\fB\-o\fR \-|NAME.xml, \fB\-\-output\-file\fR=\fI\,\-\/\fR|NAME.xml \fB\-o\fR \-|NAME.xml, \fB\-\-output\-file\fR=\fI\,\-\/\fR|NAME.xml
file to save the manifest to file to save the manifest to. (Filename prefix for
multi\-tree.)
.SS Logging options: .SS Logging options:
.TP .TP
\fB\-v\fR, \fB\-\-verbose\fR \fB\-v\fR, \fB\-\-verbose\fR
@ -48,6 +49,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help manifest` to view the detailed manual. Run `repo help manifest` to view the detailed manual.
.SH DETAILS .SH DETAILS
@ -88,6 +102,7 @@ A manifest XML file (e.g. `default.xml`) roughly conforms to the following DTD:
remote*, remote*,
default?, default?,
manifest\-server?, manifest\-server?,
submanifest*?,
remove\-project*, remove\-project*,
project*, project*,
extend\-project*, extend\-project*,
@ -118,6 +133,16 @@ include*)>
.IP .IP
<!ELEMENT manifest\-server EMPTY> <!ELEMENT manifest\-server EMPTY>
<!ATTLIST manifest\-server url CDATA #REQUIRED> <!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 .TP
<!ELEMENT project (annotation*, <!ELEMENT project (annotation*,
project*, project*,
@ -295,6 +320,65 @@ GetManifest(tag)
Return a manifest in which each project is pegged to the revision at the 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. specified tag. This is used by repo sync when the \fB\-\-smart\-tag\fR option is given.
.PP .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 Element project
.PP .PP
One or more project elements may be specified. Each element describes a single One or more project elements may be specified. Each element describes a single
@ -513,8 +597,8 @@ restrictions are not enforced for [Local Manifests].
.PP .PP
Attribute `groups`: List of additional groups to which all projects in the Attribute `groups`: List of additional groups to which all projects in the
included manifest belong. This appends and recurses, meaning all projects in included manifest belong. This appends and recurses, meaning all projects in
sub\-manifests carry all parent include groups. Same syntax as the corresponding included manifests carry all parent include groups. Same syntax as the
element of `project`. corresponding element of `project`.
.PP .PP
Local Manifests Local Manifests
.PP .PP

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo overview" "Repo Manual" .TH REPO "1" "July 2022" "repo overview" "Repo Manual"
.SH NAME .SH NAME
repo \- repo overview - manual page for repo overview repo \- repo overview - manual page for repo overview
.SH SYNOPSIS .SH SYNOPSIS
@ -26,6 +26,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help overview` to view the detailed manual. Run `repo help overview` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo prune" "Repo Manual" .TH REPO "1" "July 2022" "repo prune" "Repo Manual"
.SH NAME .SH NAME
repo \- repo prune - manual page for repo prune repo \- repo prune - manual page for repo prune
.SH SYNOPSIS .SH SYNOPSIS
@ -24,5 +24,18 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help prune` to view the detailed manual. Run `repo help prune` to view the detailed manual.

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo rebase" "Repo Manual" .TH REPO "1" "July 2022" "repo rebase" "Repo Manual"
.SH NAME .SH NAME
repo \- repo rebase - manual page for repo rebase repo \- repo rebase - manual page for repo rebase
.SH SYNOPSIS .SH SYNOPSIS
@ -46,6 +46,19 @@ only show errors
.TP .TP
\fB\-i\fR, \fB\-\-interactive\fR \fB\-i\fR, \fB\-\-interactive\fR
interactive rebase (single project only) 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 .PP
Run `repo help rebase` to view the detailed manual. Run `repo help rebase` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo selfupdate" "Repo Manual" .TH REPO "1" "July 2022" "repo selfupdate" "Repo Manual"
.SH NAME .SH NAME
repo \- repo selfupdate - manual page for repo selfupdate repo \- repo selfupdate - manual page for repo selfupdate
.SH SYNOPSIS .SH SYNOPSIS
@ -20,6 +20,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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: .SS repo Version options:
.TP .TP
\fB\-\-no\-repo\-verify\fR \fB\-\-no\-repo\-verify\fR

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "November 2021" "repo smartsync" "Repo Manual" .TH REPO "1" "August 2022" "repo smartsync" "Repo Manual"
.SH NAME .SH NAME
repo \- repo smartsync - manual page for repo smartsync repo \- repo smartsync - manual page for repo smartsync
.SH SYNOPSIS .SH SYNOPSIS
@ -20,11 +20,11 @@ number of CPU cores)
.TP .TP
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR \fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
number of network jobs to run in parallel (defaults to number of network jobs to run in parallel (defaults to
\fB\-\-jobs\fR) \fB\-\-jobs\fR or 1)
.TP .TP
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR \fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
number of local checkout jobs to run in parallel number of local checkout jobs to run in parallel
(defaults to \fB\-\-jobs\fR) (defaults to \fB\-\-jobs\fR or 8)
.TP .TP
\fB\-f\fR, \fB\-\-force\-broken\fR \fB\-f\fR, \fB\-\-force\-broken\fR
obsolete option (to be deleted in the future) obsolete option (to be deleted in the future)
@ -112,6 +112,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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: .SS repo Version options:
.TP .TP
\fB\-\-no\-repo\-verify\fR \fB\-\-no\-repo\-verify\fR

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo stage" "Repo Manual" .TH REPO "1" "July 2022" "repo stage" "Repo Manual"
.SH NAME .SH NAME
repo \- repo stage - manual page for repo stage repo \- repo stage - manual page for repo stage
.SH SYNOPSIS .SH SYNOPSIS
@ -23,6 +23,19 @@ only show errors
.TP .TP
\fB\-i\fR, \fB\-\-interactive\fR \fB\-i\fR, \fB\-\-interactive\fR
use interactive staging 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 .PP
Run `repo help stage` to view the detailed manual. Run `repo help stage` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo start" "Repo Manual" .TH REPO "1" "July 2022" "repo start" "Repo Manual"
.SH NAME .SH NAME
repo \- repo start - manual page for repo start repo \- repo start - manual page for repo start
.SH SYNOPSIS .SH SYNOPSIS
@ -33,6 +33,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help start` to view the detailed manual. Run `repo help start` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo status" "Repo Manual" .TH REPO "1" "July 2022" "repo status" "Repo Manual"
.SH NAME .SH NAME
repo \- repo status - manual page for repo status repo \- repo status - manual page for repo status
.SH SYNOPSIS .SH SYNOPSIS
@ -28,6 +28,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help status` to view the detailed manual. Run `repo help status` to view the detailed manual.
.SH DETAILS .SH DETAILS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "November 2021" "repo sync" "Repo Manual" .TH REPO "1" "August 2022" "repo sync" "Repo Manual"
.SH NAME .SH NAME
repo \- repo sync - manual page for repo sync repo \- repo sync - manual page for repo sync
.SH SYNOPSIS .SH SYNOPSIS
@ -20,11 +20,11 @@ number of CPU cores)
.TP .TP
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR \fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
number of network jobs to run in parallel (defaults to number of network jobs to run in parallel (defaults to
\fB\-\-jobs\fR) \fB\-\-jobs\fR or 1)
.TP .TP
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR \fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
number of local checkout jobs to run in parallel number of local checkout jobs to run in parallel
(defaults to \fB\-\-jobs\fR) (defaults to \fB\-\-jobs\fR or 8)
.TP .TP
\fB\-f\fR, \fB\-\-force\-broken\fR \fB\-f\fR, \fB\-\-force\-broken\fR
obsolete option (to be deleted in the future) obsolete option (to be deleted in the future)
@ -119,6 +119,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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: .SS repo Version options:
.TP .TP
\fB\-\-no\-repo\-verify\fR \fB\-\-no\-repo\-verify\fR

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo upload" "Repo Manual" .TH REPO "1" "August 2022" "repo upload" "Repo Manual"
.SH NAME .SH NAME
repo \- repo upload - manual page for repo upload repo \- repo upload - manual page for repo upload
.SH SYNOPSIS .SH SYNOPSIS
@ -54,6 +54,9 @@ upload as a private change (deprecated; use \fB\-\-wip\fR)
\fB\-w\fR, \fB\-\-wip\fR \fB\-w\fR, \fB\-\-wip\fR
upload as a work\-in\-progress change upload as a work\-in\-progress change
.TP .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 \fB\-o\fR PUSH_OPTIONS, \fB\-\-push\-option\fR=\fI\,PUSH_OPTIONS\/\fR
additional push options to transmit additional push options to transmit
.TP .TP
@ -66,6 +69,12 @@ do everything except actually upload the CL
\fB\-y\fR, \fB\-\-yes\fR \fB\-y\fR, \fB\-\-yes\fR
answer yes to all safe prompts answer yes to all safe prompts
.TP .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 \fB\-\-no\-cert\-checks\fR
disable verifying ssl certs (unsafe) disable verifying ssl certs (unsafe)
.SS Logging options: .SS Logging options:
@ -75,6 +84,19 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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: .SS pre\-upload hooks:
.TP .TP
\fB\-\-no\-verify\fR \fB\-\-no\-verify\fR
@ -105,6 +127,12 @@ 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 \fB\-\-reviewers\fR must already be registered with the code review system, or the
upload will fail. upload will fail.
.PP .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 Configuration
.PP .PP
review.URL.autoupload: review.URL.autoupload:

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "July 2021" "repo version" "Repo Manual" .TH REPO "1" "July 2022" "repo version" "Repo Manual"
.SH NAME .SH NAME
repo \- repo version - manual page for repo version repo \- repo version - manual page for repo version
.SH SYNOPSIS .SH SYNOPSIS
@ -20,5 +20,18 @@ show all output
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
only show errors 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 .PP
Run `repo help version` to view the detailed manual. Run `repo help version` to view the detailed manual.

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man. .\" DO NOT MODIFY THIS FILE! It was generated by help2man.
.TH REPO "1" "November 2021" "repo" "Repo Manual" .TH REPO "1" "July 2022" "repo" "Repo Manual"
.SH NAME .SH NAME
repo \- repository management tool built on top of git repo \- repository management tool built on top of git
.SH SYNOPSIS .SH SYNOPSIS
@ -43,6 +43,9 @@ filename of event log to append timeline to
.TP .TP
\fB\-\-git\-trace2\-event\-log\fR=\fI\,GIT_TRACE2_EVENT_LOG\/\fR \fB\-\-git\-trace2\-event\-log\fR=\fI\,GIT_TRACE2_EVENT_LOG\/\fR
directory to write git trace2 event log to 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:" .SS "The complete list of recognized repo commands is:"
.TP .TP
abandon abandon

View File

@ -123,7 +123,7 @@ class _Default(object):
destBranchExpr = None destBranchExpr = None
upstreamExpr = None upstreamExpr = None
remote = None remote = None
sync_j = 1 sync_j = None
sync_c = False sync_c = False
sync_s = False sync_s = False
sync_tags = True sync_tags = True
@ -214,6 +214,7 @@ class _XmlSubmanifest:
revision: a string, the commitish. revision: a string, the commitish.
manifestName: a string, the submanifest file name. manifestName: a string, the submanifest file name.
groups: a list of strings, the groups to add to all projects in the submanifest. groups: a list of strings, the groups to add to all projects in the submanifest.
default_groups: a list of strings, the default groups to sync.
path: a string, the relative path for the submanifest checkout. path: a string, the relative path for the submanifest checkout.
parent: an XmlManifest, the parent manifest. parent: an XmlManifest, the parent manifest.
annotations: (derived) a list of annotations. annotations: (derived) a list of annotations.
@ -226,6 +227,7 @@ class _XmlSubmanifest:
revision=None, revision=None,
manifestName=None, manifestName=None,
groups=None, groups=None,
default_groups=None,
path=None, path=None,
parent=None): parent=None):
self.name = name self.name = name
@ -234,6 +236,7 @@ class _XmlSubmanifest:
self.revision = revision self.revision = revision
self.manifestName = manifestName self.manifestName = manifestName
self.groups = groups self.groups = groups
self.default_groups = default_groups
self.path = path self.path = path
self.parent = parent self.parent = parent
self.annotations = [] self.annotations = []
@ -250,7 +253,8 @@ class _XmlSubmanifest:
os.path.join(parent.path_prefix, self.relpath), MANIFEST_FILE_NAME) os.path.join(parent.path_prefix, self.relpath), MANIFEST_FILE_NAME)
rc = self.repo_client = RepoClient( rc = self.repo_client = RepoClient(
parent.repodir, linkFile, parent_groups=','.join(groups) or '', parent.repodir, linkFile, parent_groups=','.join(groups) or '',
submanifest_path=self.relpath, outer_client=outer_client) submanifest_path=self.relpath, outer_client=outer_client,
default_groups=default_groups)
self.present = os.path.exists(manifestFile) self.present = os.path.exists(manifestFile)
@ -264,6 +268,7 @@ class _XmlSubmanifest:
self.revision == other.revision and self.revision == other.revision and
self.manifestName == other.manifestName and self.manifestName == other.manifestName and
self.groups == other.groups and self.groups == other.groups and
self.default_groups == other.default_groups and
self.path == other.path and self.path == other.path and
sorted(self.annotations) == sorted(other.annotations)) sorted(self.annotations) == sorted(other.annotations))
@ -279,11 +284,12 @@ class _XmlSubmanifest:
if self.project: if self.project:
manifestUrl = remote.ToRemoteSpec(self.project).url manifestUrl = remote.ToRemoteSpec(self.project).url
else: else:
manifestUrl = mp.GetRemote(mp.remote.name).url manifestUrl = mp.GetRemote().url
manifestName = self.manifestName or 'default.xml' manifestName = self.manifestName or 'default.xml'
revision = self.revision or self.name revision = self.revision or self.name
path = self.path or revision.split('/')[-1] path = self.path or revision.split('/')[-1]
groups = self.groups or [] groups = self.groups or []
default_groups = self.default_groups or []
return SubmanifestSpec(self.name, manifestUrl, manifestName, revision, path, return SubmanifestSpec(self.name, manifestUrl, manifestName, revision, path,
groups) groups)
@ -300,6 +306,10 @@ class _XmlSubmanifest:
return ','.join(self.groups) return ','.join(self.groups)
return '' return ''
def GetDefaultGroupsStr(self):
"""Returns the `default-groups` given for this submanifest."""
return ','.join(self.default_groups or [])
def AddAnnotation(self, name, value, keep): def AddAnnotation(self, name, value, keep):
"""Add annotations to the submanifest.""" """Add annotations to the submanifest."""
self.annotations.append(Annotation(name, value, keep)) self.annotations.append(Annotation(name, value, keep))
@ -327,7 +337,8 @@ class XmlManifest(object):
"""manages the repo configuration file""" """manages the repo configuration file"""
def __init__(self, repodir, manifest_file, local_manifests=None, def __init__(self, repodir, manifest_file, local_manifests=None,
outer_client=None, parent_groups='', submanifest_path=''): outer_client=None, parent_groups='', submanifest_path='',
default_groups=None):
"""Initialize. """Initialize.
Args: Args:
@ -337,16 +348,20 @@ class XmlManifest(object):
be |repodir|/|MANIFEST_FILE_NAME|. be |repodir|/|MANIFEST_FILE_NAME|.
local_manifests: Full path to the directory of local override manifests. local_manifests: Full path to the directory of local override manifests.
This will usually be |repodir|/|LOCAL_MANIFESTS_DIR_NAME|. This will usually be |repodir|/|LOCAL_MANIFESTS_DIR_NAME|.
outer_client: RepoClient of the outertree. outer_client: RepoClient of the outer manifest.
parent_groups: a string, the groups to apply to this projects. parent_groups: a string, the groups to apply to this projects.
submanifest_path: The submanifest root relative to the repo root. submanifest_path: The submanifest root relative to the repo root.
default_groups: a string, the default manifest groups to use.
""" """
# TODO(vapier): Move this out of this class. # TODO(vapier): Move this out of this class.
self.globalConfig = GitConfig.ForUser() self.globalConfig = GitConfig.ForUser()
self.repodir = os.path.abspath(repodir) self.repodir = os.path.abspath(repodir)
self._CheckLocalPath(submanifest_path) self._CheckLocalPath(submanifest_path)
self.topdir = os.path.join(os.path.dirname(self.repodir), submanifest_path) self.topdir = os.path.dirname(self.repodir)
if submanifest_path:
# This avoids a trailing os.path.sep when submanifest_path is empty.
self.topdir = os.path.join(self.topdir, submanifest_path)
if manifest_file != os.path.abspath(manifest_file): if manifest_file != os.path.abspath(manifest_file):
raise ManifestParseError('manifest_file must be abspath') raise ManifestParseError('manifest_file must be abspath')
self.manifestFile = manifest_file self.manifestFile = manifest_file
@ -358,6 +373,7 @@ class XmlManifest(object):
self.local_manifests = local_manifests self.local_manifests = local_manifests
self._load_local_manifests = True self._load_local_manifests = True
self.parent_groups = parent_groups self.parent_groups = parent_groups
self.default_groups = default_groups
if outer_client and self.isGitcClient: if outer_client and self.isGitcClient:
raise ManifestParseError('Multi-manifest is incompatible with `gitc-init`') raise ManifestParseError('Multi-manifest is incompatible with `gitc-init`')
@ -472,6 +488,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
e.setAttribute('path', r.path) e.setAttribute('path', r.path)
if r.groups: if r.groups:
e.setAttribute('groups', r.GetGroupsStr()) e.setAttribute('groups', r.GetGroupsStr())
if r.default_groups:
e.setAttribute('default-groups', r.GetDefaultGroupsStr())
for a in r.annotations: for a in r.annotations:
if a.keep == 'true': if a.keep == 'true':
@ -487,7 +505,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
""" """
return [x for x in re.split(r'[,\s]+', field) if x] return [x for x in re.split(r'[,\s]+', field) if x]
def ToXml(self, peg_rev=False, peg_rev_upstream=True, peg_rev_dest_branch=True, groups=None): def ToXml(self, peg_rev=False, peg_rev_upstream=True,
peg_rev_dest_branch=True, groups=None, omit_local=False):
"""Return the current manifest XML.""" """Return the current manifest XML."""
mp = self.manifestProject mp = self.manifestProject
@ -532,7 +551,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if d.upstreamExpr: if d.upstreamExpr:
have_default = True have_default = True
e.setAttribute('upstream', d.upstreamExpr) e.setAttribute('upstream', d.upstreamExpr)
if d.sync_j > 1: if d.sync_j is not None:
have_default = True have_default = True
e.setAttribute('sync-j', '%d' % d.sync_j) e.setAttribute('sync-j', '%d' % d.sync_j)
if d.sync_c: if d.sync_c:
@ -568,6 +587,9 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if not p.MatchesGroups(groups): if not p.MatchesGroups(groups):
return return
if omit_local and self.IsFromLocalManifest(p):
return
name = p.name name = p.name
relpath = p.relpath relpath = p.relpath
if parent: if parent:
@ -753,23 +775,29 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
@property @property
def is_multimanifest(self): def is_multimanifest(self):
"""Whether this is a multimanifest checkout""" """Whether this is a multimanifest checkout.
return bool(self.outer_client.submanifests)
This is safe to use as long as the outermost manifest XML has been parsed.
"""
return bool(self._outer_client._submanifests)
@property @property
def is_submanifest(self): def is_submanifest(self):
"""Whether this manifest is a submanifest""" """Whether this manifest is a submanifest.
This is safe to use as long as the outermost manifest XML has been parsed.
"""
return self._outer_client and self._outer_client != self return self._outer_client and self._outer_client != self
@property @property
def outer_client(self): def outer_client(self):
"""The instance of the outermost manifest client""" """The instance of the outermost manifest client."""
self._Load() self._Load()
return self._outer_client return self._outer_client
@property @property
def all_manifests(self): def all_manifests(self):
"""Generator yielding all (sub)manifests.""" """Generator yielding all (sub)manifests, in depth-first order."""
self._Load() self._Load()
outer = self._outer_client outer = self._outer_client
yield outer yield outer
@ -778,7 +806,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
@property @property
def all_children(self): def all_children(self):
"""Generator yielding all child submanifests.""" """Generator yielding all (present) child submanifests."""
self._Load() self._Load()
for child in self._submanifests.values(): for child in self._submanifests.values():
if child.repo_client: if child.repo_client:
@ -795,7 +823,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
@property @property
def all_paths(self): def all_paths(self):
"""All project paths for all (sub)manifests. See `paths`.""" """All project paths for all (sub)manifests.
See also `paths`.
Returns:
A dictionary of {path: Project()}. `path` is relative to the outer
manifest.
"""
ret = {} ret = {}
for tree in self.all_manifests: for tree in self.all_manifests:
prefix = tree.path_prefix prefix = tree.path_prefix
@ -811,7 +846,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
def paths(self): def paths(self):
"""Return all paths for this manifest. """Return all paths for this manifest.
Return: Returns:
A dictionary of {path: Project()}. `path` is relative to this manifest. A dictionary of {path: Project()}. `path` is relative to this manifest.
""" """
self._Load() self._Load()
@ -825,11 +860,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
@property @property
def remotes(self): def remotes(self):
"""Return a list of remotes for this manifest."""
self._Load() self._Load()
return self._remotes return self._remotes
@property @property
def default(self): def default(self):
"""Return default values for this manifest."""
self._Load() self._Load()
return self._default return self._default
@ -967,16 +1004,21 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
worktree=os.path.join(subdir, 'manifests')) worktree=os.path.join(subdir, 'manifests'))
return mp return mp
def GetDefaultGroupsStr(self): def GetDefaultGroupsStr(self, with_platform=True):
"""Returns the default group string for the platform.""" """Returns the default group string to use.
return 'default,platform-' + platform.system().lower()
Args:
with_platform: a boolean, whether to include the group for the
underlying platform.
"""
groups = ','.join(self.default_groups or ['default'])
if with_platform:
groups += f',platform-{platform.system().lower()}'
return groups
def GetGroupsStr(self): def GetGroupsStr(self):
"""Returns the manifest group string that should be synced.""" """Returns the manifest group string that should be synced."""
groups = self.manifestProject.manifest_groups return self.manifestProject.manifest_groups or self.GetDefaultGroupsStr()
if not groups:
groups = self.GetDefaultGroupsStr()
return groups
def Unload(self): def Unload(self):
"""Unload the manifest. """Unload the manifest.
@ -1067,8 +1109,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if override: if override:
self.manifestFile = savedManifestFile self.manifestFile = savedManifestFile
# Now that we have loaded this manifest, load any submanifest manifests # Now that we have loaded this manifest, load any submanifests as well.
# as well. We need to do this after self._loaded is set to avoid looping. # We need to do this after self._loaded is set to avoid looping.
for name in self._submanifests: for name in self._submanifests:
tree = self._submanifests[name] tree = self._submanifests[name]
spec = tree.ToSubmanifestSpec() spec = tree.ToSubmanifestSpec()
@ -1346,7 +1388,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
def _AddMetaProjectMirror(self, m): def _AddMetaProjectMirror(self, m):
name = None name = None
m_url = m.GetRemote(m.remote.name).url m_url = m.GetRemote().url
if m_url.endswith('/.git'): if m_url.endswith('/.git'):
raise ManifestParseError('refusing to mirror %s' % m_url) raise ManifestParseError('refusing to mirror %s' % m_url)
@ -1423,8 +1465,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
d.destBranchExpr = node.getAttribute('dest-branch') or None d.destBranchExpr = node.getAttribute('dest-branch') or None
d.upstreamExpr = node.getAttribute('upstream') or None d.upstreamExpr = node.getAttribute('upstream') or None
d.sync_j = XmlInt(node, 'sync-j', 1) d.sync_j = XmlInt(node, 'sync-j', None)
if d.sync_j <= 0: if d.sync_j is not None and d.sync_j <= 0:
raise ManifestParseError('%s: sync-j must be greater than 0, not "%s"' % raise ManifestParseError('%s: sync-j must be greater than 0, not "%s"' %
(self.manifestFile, d.sync_j)) (self.manifestFile, d.sync_j))
@ -1491,6 +1533,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if node.hasAttribute('groups'): if node.hasAttribute('groups'):
groups = node.getAttribute('groups') groups = node.getAttribute('groups')
groups = self._ParseList(groups) groups = self._ParseList(groups)
default_groups = self._ParseList(node.getAttribute('default-groups'))
path = node.getAttribute('path') path = node.getAttribute('path')
if path == '': if path == '':
path = None path = None
@ -1511,7 +1554,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
'<submanifest> invalid "path": %s: %s' % (path, msg)) '<submanifest> invalid "path": %s: %s' % (path, msg))
submanifest = _XmlSubmanifest(name, remote, project, revision, manifestName, submanifest = _XmlSubmanifest(name, remote, project, revision, manifestName,
groups, path, self) groups, default_groups, path, self)
for n in node.childNodes: for n in node.childNodes:
if n.nodeName == 'annotation': if n.nodeName == 'annotation':
@ -1635,6 +1678,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
name: a string, the name of the project. name: a string, the name of the project.
path: a string, the path of the project. path: a string, the path of the project.
remote: a string, the remote.name of the project. remote: a string, the remote.name of the project.
Returns:
A tuple of (relpath, worktree, gitdir, objdir, use_git_worktrees) for the
project with |name| and |path|.
""" """
# The manifest entries might have trailing slashes. Normalize them to avoid # The manifest entries might have trailing slashes. Normalize them to avoid
# unexpected filesystem behavior since we do string concatenation below. # unexpected filesystem behavior since we do string concatenation below.
@ -1642,7 +1689,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
name = name.rstrip('/') name = name.rstrip('/')
remote = remote.rstrip('/') remote = remote.rstrip('/')
use_git_worktrees = False use_git_worktrees = False
use_remote_name = bool(self._outer_client._submanifests) use_remote_name = self.is_multimanifest
relpath = path relpath = path
if self.IsMirror: if self.IsMirror:
worktree = None worktree = None
@ -1658,7 +1705,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
# We allow people to mix git worktrees & non-git worktrees for now. # We allow people to mix git worktrees & non-git worktrees for now.
# This allows for in situ migration of repo clients. # This allows for in situ migration of repo clients.
if os.path.exists(gitdir) or not self.UseGitWorktrees: if os.path.exists(gitdir) or not self.UseGitWorktrees:
objdir = os.path.join(self.subdir, 'project-objects', namepath) objdir = os.path.join(self.repodir, 'project-objects', namepath)
else: else:
use_git_worktrees = True use_git_worktrees = True
gitdir = os.path.join(self.repodir, 'worktrees', namepath) gitdir = os.path.join(self.repodir, 'worktrees', namepath)
@ -1672,6 +1719,9 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
name: a string, the name of the project. name: a string, the name of the project.
all_manifests: a boolean, if True, then all manifests are searched. If all_manifests: a boolean, if True, then all manifests are searched. If
False, then only this manifest is searched. False, then only this manifest is searched.
Returns:
A list of Project instances with name |name|.
""" """
if all_manifests: if all_manifests:
return list(itertools.chain.from_iterable( return list(itertools.chain.from_iterable(
@ -1932,6 +1982,16 @@ class RepoClient(XmlManifest):
"""Manages a repo client checkout.""" """Manages a repo client checkout."""
def __init__(self, repodir, manifest_file=None, submanifest_path='', **kwargs): def __init__(self, repodir, manifest_file=None, submanifest_path='', **kwargs):
"""Initialize.
Args:
repodir: Path to the .repo/ dir for holding all internal checkout state.
It must be in the top directory of the repo client checkout.
manifest_file: Full path to the manifest file to parse. This will usually
be |repodir|/|MANIFEST_FILE_NAME|.
submanifest_path: The submanifest root relative to the repo root.
**kwargs: Additional keyword arguments, passed to XmlManifest.
"""
self.isGitcClient = False self.isGitcClient = False
submanifest_path = submanifest_path or '' submanifest_path = submanifest_path or ''
if submanifest_path: if submanifest_path:

View File

@ -56,8 +56,11 @@ def _PipePager(pager):
global pager_process, old_stdout, old_stderr global pager_process, old_stdout, old_stderr
assert pager_process is None, "Only one active pager process at a time" assert pager_process is None, "Only one active pager process at a time"
# Create pager process, piping stdout/err into its stdin # Create pager process, piping stdout/err into its stdin
pager_process = subprocess.Popen([pager], stdin=subprocess.PIPE, stdout=sys.stdout, try:
stderr=sys.stderr) 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_stdout = sys.stdout
old_stderr = sys.stderr old_stderr = sys.stderr
sys.stdout = pager_process.stdin sys.stdout = pager_process.stdin

View File

@ -29,9 +29,11 @@ import time
import urllib.parse import urllib.parse
from color import Coloring from color import Coloring
import fetch
from git_command import GitCommand, git_require from git_command import GitCommand, git_require
from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \ from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
ID_RE ID_RE
import git_superproject
from git_trace2_event_log import EventLog from git_trace2_event_log import EventLog
from error import GitError, UploadError, DownloadError from error import GitError, UploadError, DownloadError
from error import ManifestInvalidRevisionError, ManifestInvalidPathError from error import ManifestInvalidRevisionError, ManifestInvalidPathError
@ -48,6 +50,9 @@ MAXIMUM_RETRY_SLEEP_SEC = 3600.0
# +-10% random jitter is added to each Fetches retry sleep duration. # +-10% random jitter is added to each Fetches retry sleep duration.
RETRY_JITTER_PERCENT = 0.1 RETRY_JITTER_PERCENT = 0.1
# Whether to use alternates.
# TODO(vapier): Remove knob once behavior is verified.
_ALTERNATES = os.environ.get('REPO_USE_ALTERNATES') == '1'
def _lwrite(path, content): def _lwrite(path, content):
lock = '%s.lock' % path lock = '%s.lock' % path
@ -200,6 +205,7 @@ class ReviewableBranch(object):
private=False, private=False,
notify=None, notify=None,
wip=False, wip=False,
ready=False,
dest_branch=None, dest_branch=None,
validate_certs=True, validate_certs=True,
push_options=None): push_options=None):
@ -212,6 +218,7 @@ class ReviewableBranch(object):
private=private, private=private,
notify=notify, notify=notify,
wip=wip, wip=wip,
ready=ready,
dest_branch=dest_branch, dest_branch=dest_branch,
validate_certs=validate_certs, validate_certs=validate_certs,
push_options=push_options) push_options=push_options)
@ -459,7 +466,13 @@ class RemoteSpec(object):
class Project(object): class Project(object):
# These objects can be shared between several working trees. # These objects can be shared between several working trees.
shareable_dirs = ['hooks', 'objects', 'rr-cache'] @property
def shareable_dirs(self):
"""Return the shareable directories"""
if self.UseAlternates:
return ['hooks', 'rr-cache']
else:
return ['hooks', 'objects', 'rr-cache']
def __init__(self, def __init__(self,
manifest, manifest,
@ -589,6 +602,14 @@ class Project(object):
self.bare_ref = GitRefs(self.gitdir) self.bare_ref = GitRefs(self.gitdir)
self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir) self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
@property
def UseAlternates(self):
"""Whether git alternates are in use.
This will be removed once migration to alternates is complete.
"""
return _ALTERNATES or self.manifest.is_multimanifest
@property @property
def Derived(self): def Derived(self):
return self.is_derived return self.is_derived
@ -631,7 +652,7 @@ class Project(object):
return True return True
if self.work_git.DiffZ('diff-files'): if self.work_git.DiffZ('diff-files'):
return True return True
if consider_untracked and self.work_git.LsOthers(): if consider_untracked and self.UntrackedFiles():
return True return True
return False return False
@ -665,9 +686,13 @@ class Project(object):
self._userident_name = '' self._userident_name = ''
self._userident_email = '' self._userident_email = ''
def GetRemote(self, name): def GetRemote(self, name=None):
"""Get the configuration for a single remote. """Get the configuration for a single remote.
Defaults to the current project's remote.
""" """
if name is None:
name = self.remote.name
return self.config.GetRemote(name) return self.config.GetRemote(name)
def GetBranch(self, name): def GetBranch(self, name):
@ -714,7 +739,8 @@ class Project(object):
The special manifest group "default" will match any project that The special manifest group "default" will match any project that
does not have the special project group "notdefault" does not have the special project group "notdefault"
""" """
expanded_manifest_groups = manifest_groups or ['default'] default_groups = self.manifest.default_groups or ['default']
expanded_manifest_groups = manifest_groups or default_groups
expanded_project_groups = ['all'] + (self.groups or []) expanded_project_groups = ['all'] + (self.groups or [])
if 'notdefault' not in expanded_project_groups: if 'notdefault' not in expanded_project_groups:
expanded_project_groups += ['default'] expanded_project_groups += ['default']
@ -759,33 +785,37 @@ class Project(object):
if not get_all: if not get_all:
return details return details
changes = self.work_git.LsOthers() changes = self.UntrackedFiles()
if changes: if changes:
details.extend(changes) details.extend(changes)
return details return details
def UntrackedFiles(self):
"""Returns a list of strings, untracked files in the git tree."""
return self.work_git.LsOthers()
def HasChanges(self): def HasChanges(self):
"""Returns true if there are uncommitted changes. """Returns true if there are uncommitted changes.
""" """
if self.UncommitedFiles(get_all=False): return bool(self.UncommitedFiles(get_all=False))
return True
else:
return False
def PrintWorkTreeStatus(self, output_redir=None, quiet=False): def PrintWorkTreeStatus(self, output_redir=None, quiet=False, local=False):
"""Prints the status of the repository to stdout. """Prints the status of the repository to stdout.
Args: Args:
output_redir: If specified, redirect the output to this object. output_redir: If specified, redirect the output to this object.
quiet: If True then only print the project name. Do not print quiet: If True then only print the project name. Do not print
the modified files, branch name, etc. the modified files, branch name, etc.
local: a boolean, if True, the path is relative to the local
(sub)manifest. If false, the path is relative to the
outermost manifest.
""" """
if not platform_utils.isdir(self.worktree): if not platform_utils.isdir(self.worktree):
if output_redir is None: if output_redir is None:
output_redir = sys.stdout output_redir = sys.stdout
print(file=output_redir) print(file=output_redir)
print('project %s/' % self.relpath, file=output_redir) print('project %s/' % self.RelPath(local), file=output_redir)
print(' missing (run "repo sync")', file=output_redir) print(' missing (run "repo sync")', file=output_redir)
return return
@ -803,7 +833,7 @@ class Project(object):
out = StatusColoring(self.config) out = StatusColoring(self.config)
if output_redir is not None: if output_redir is not None:
out.redirect(output_redir) out.redirect(output_redir)
out.project('project %-40s', self.relpath + '/ ') out.project('project %-40s', self.RelPath(local) + '/ ')
if quiet: if quiet:
out.nl() out.nl()
@ -864,7 +894,8 @@ class Project(object):
return 'DIRTY' return 'DIRTY'
def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None): def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None,
local=False):
"""Prints the status of the repository to stdout. """Prints the status of the repository to stdout.
""" """
out = DiffColoring(self.config) out = DiffColoring(self.config)
@ -875,8 +906,8 @@ class Project(object):
cmd.append('--color') cmd.append('--color')
cmd.append(HEAD) cmd.append(HEAD)
if absolute_paths: if absolute_paths:
cmd.append('--src-prefix=a/%s/' % self.relpath) cmd.append('--src-prefix=a/%s/' % self.RelPath(local))
cmd.append('--dst-prefix=b/%s/' % self.relpath) cmd.append('--dst-prefix=b/%s/' % self.RelPath(local))
cmd.append('--') cmd.append('--')
try: try:
p = GitCommand(self, p = GitCommand(self,
@ -886,14 +917,14 @@ class Project(object):
p.Wait() p.Wait()
except GitError as e: except GitError as e:
out.nl() out.nl()
out.project('project %s/' % self.relpath) out.project('project %s/' % self.RelPath(local))
out.nl() out.nl()
out.fail('%s', str(e)) out.fail('%s', str(e))
out.nl() out.nl()
return False return False
if p.stdout: if p.stdout:
out.nl() out.nl()
out.project('project %s/' % self.relpath) out.project('project %s/' % self.RelPath(local))
out.nl() out.nl()
out.write('%s', p.stdout) out.write('%s', p.stdout)
return p.Wait() == 0 return p.Wait() == 0
@ -978,6 +1009,7 @@ class Project(object):
private=False, private=False,
notify=None, notify=None,
wip=False, wip=False,
ready=False,
dest_branch=None, dest_branch=None,
validate_certs=True, validate_certs=True,
push_options=None): push_options=None):
@ -994,6 +1026,13 @@ class Project(object):
if not branch.remote.review: if not branch.remote.review:
raise GitError('remote %s has no review url' % branch.remote.name) raise GitError('remote %s has no review url' % branch.remote.name)
# Basic validity check on label syntax.
for label in labels:
if not re.match(r'^.+[+-][0-9]+$', label):
raise UploadError(
f'invalid label syntax "{label}": labels use forms like '
'CodeReview+1 or Verified-1')
if dest_branch is None: if dest_branch is None:
dest_branch = self.dest_branch dest_branch = self.dest_branch
if dest_branch is None: if dest_branch is None:
@ -1029,6 +1068,7 @@ class Project(object):
if auto_topic: if auto_topic:
opts += ['topic=' + branch.name] opts += ['topic=' + branch.name]
opts += ['t=%s' % p for p in hashtags] opts += ['t=%s' % p for p in hashtags]
# NB: No need to encode labels as they've been validated above.
opts += ['l=%s' % p for p in labels] opts += ['l=%s' % p for p in labels]
opts += ['r=%s' % p for p in people[0]] opts += ['r=%s' % p for p in people[0]]
@ -1039,6 +1079,8 @@ class Project(object):
opts += ['private'] opts += ['private']
if wip: if wip:
opts += ['wip'] opts += ['wip']
if ready:
opts += ['ready']
if opts: if opts:
ref_spec = ref_spec + '%' + ','.join(opts) ref_spec = ref_spec + '%' + ','.join(opts)
cmd.append(ref_spec) cmd.append(ref_spec)
@ -1133,6 +1175,17 @@ class Project(object):
self._UpdateHooks(quiet=quiet) self._UpdateHooks(quiet=quiet)
self._InitRemote() self._InitRemote()
if self.UseAlternates:
# If gitdir/objects is a symlink, migrate it from the old layout.
gitdir_objects = os.path.join(self.gitdir, 'objects')
if platform_utils.islink(gitdir_objects):
platform_utils.remove(gitdir_objects, missing_ok=True)
gitdir_alt = os.path.join(self.gitdir, 'objects/info/alternates')
if not os.path.exists(gitdir_alt):
os.makedirs(os.path.dirname(gitdir_alt), exist_ok=True)
_lwrite(gitdir_alt, os.path.join(
os.path.relpath(self.objdir, gitdir_objects), 'objects') + '\n')
if is_new: if is_new:
alt = os.path.join(self.objdir, 'objects/info/alternates') alt = os.path.join(self.objdir, 'objects/info/alternates')
try: try:
@ -1233,7 +1286,7 @@ class Project(object):
if self.revisionId: if self.revisionId:
return self.revisionId return self.revisionId
rem = self.GetRemote(self.remote.name) rem = self.GetRemote()
rev = rem.ToLocal(self.revisionExpr) rev = rem.ToLocal(self.revisionExpr)
if all_refs is not None and rev in all_refs: if all_refs is not None and rev in all_refs:
@ -1425,7 +1478,7 @@ class Project(object):
"discarding %d commits removed from upstream", "discarding %d commits removed from upstream",
len(local_changes) - cnt_mine) len(local_changes) - cnt_mine)
branch.remote = self.GetRemote(self.remote.name) branch.remote = self.GetRemote()
if not ID_RE.match(self.revisionExpr): if not ID_RE.match(self.revisionExpr):
# in case of manifest sync the revisionExpr might be a SHA1 # in case of manifest sync the revisionExpr might be a SHA1
branch.merge = self.revisionExpr branch.merge = self.revisionExpr
@ -1483,7 +1536,7 @@ class Project(object):
def DownloadPatchSet(self, change_id, patch_id): def DownloadPatchSet(self, change_id, patch_id):
"""Download a single patch set of a single change to FETCH_HEAD. """Download a single patch set of a single change to FETCH_HEAD.
""" """
remote = self.GetRemote(self.remote.name) remote = self.GetRemote()
cmd = ['fetch', remote.name] cmd = ['fetch', remote.name]
cmd.append('refs/changes/%2.2d/%d/%d' cmd.append('refs/changes/%2.2d/%d/%d'
@ -1513,14 +1566,14 @@ class Project(object):
if self.IsDirty(): if self.IsDirty():
if force: if force:
print('warning: %s: Removing dirty project: uncommitted changes lost.' % print('warning: %s: Removing dirty project: uncommitted changes lost.' %
(self.relpath,), file=sys.stderr) (self.RelPath(local=False),), file=sys.stderr)
else: else:
print('error: %s: Cannot remove project: uncommitted changes are ' print('error: %s: Cannot remove project: uncommitted changes are '
'present.\n' % (self.relpath,), file=sys.stderr) 'present.\n' % (self.RelPath(local=False),), file=sys.stderr)
return False return False
if not quiet: if not quiet:
print('%s: Deleting obsolete checkout.' % (self.relpath,)) print('%s: Deleting obsolete checkout.' % (self.RelPath(local=False),))
# Unlock and delink from the main worktree. We don't use git's worktree # Unlock and delink from the main worktree. We don't use git's worktree
# remove because it will recursively delete projects -- we handle that # remove because it will recursively delete projects -- we handle that
@ -1559,7 +1612,8 @@ class Project(object):
if e.errno != errno.ENOENT: if e.errno != errno.ENOENT:
print('error: %s: %s' % (self.gitdir, e), file=sys.stderr) print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
print('error: %s: Failed to delete obsolete checkout; remove manually, ' print('error: %s: Failed to delete obsolete checkout; remove manually, '
'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr) 'then run `repo sync -l`.' % (self.RelPath(local=False),),
file=sys.stderr)
return False return False
# Delete everything under the worktree, except for directories that contain # Delete everything under the worktree, except for directories that contain
@ -1595,7 +1649,7 @@ class Project(object):
print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr) print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
failed = True failed = True
if failed: if failed:
print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,), print('error: %s: Failed to delete obsolete checkout.' % (self.RelPath(local=False),),
file=sys.stderr) file=sys.stderr)
print(' Remove manually, then run `repo sync -l`.', file=sys.stderr) print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
return False return False
@ -1624,13 +1678,10 @@ class Project(object):
all_refs = self.bare_ref.all all_refs = self.bare_ref.all
if R_HEADS + name in all_refs: if R_HEADS + name in all_refs:
return GitCommand(self, return GitCommand(self, ['checkout', '-q', name, '--']).Wait() == 0
['checkout', name, '--'],
capture_stdout=True,
capture_stderr=True).Wait() == 0
branch = self.GetBranch(name) branch = self.GetBranch(name)
branch.remote = self.GetRemote(self.remote.name) branch.remote = self.GetRemote()
branch.merge = branch_merge branch.merge = branch_merge
if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge): if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
branch.merge = R_HEADS + branch_merge branch.merge = R_HEADS + branch_merge
@ -1652,10 +1703,7 @@ class Project(object):
branch.Save() branch.Save()
return True return True
if GitCommand(self, if GitCommand(self, ['checkout', '-q', '-b', branch.name, revid]).Wait() == 0:
['checkout', '-b', branch.name, revid],
capture_stdout=True,
capture_stderr=True).Wait() == 0:
branch.Save() branch.Save()
return True return True
return False return False
@ -1998,7 +2046,7 @@ class Project(object):
self.bare_git.rev_list('-1', '--missing=allow-any', self.bare_git.rev_list('-1', '--missing=allow-any',
'%s^0' % self.revisionExpr, '--') '%s^0' % self.revisionExpr, '--')
if self.upstream: if self.upstream:
rev = self.GetRemote(self.remote.name).ToLocal(self.upstream) rev = self.GetRemote().ToLocal(self.upstream)
self.bare_git.rev_list('-1', '--missing=allow-any', self.bare_git.rev_list('-1', '--missing=allow-any',
'%s^0' % rev, '--') '%s^0' % rev, '--')
self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev) self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
@ -2010,7 +2058,7 @@ class Project(object):
def _FetchArchive(self, tarpath, cwd=None): def _FetchArchive(self, tarpath, cwd=None):
cmd = ['archive', '-v', '-o', tarpath] cmd = ['archive', '-v', '-o', tarpath]
cmd.append('--remote=%s' % self.remote.url) cmd.append('--remote=%s' % self.remote.url)
cmd.append('--prefix=%s/' % self.relpath) cmd.append('--prefix=%s/' % self.RelPath(local=False))
cmd.append(self.revisionExpr) cmd.append(self.revisionExpr)
command = GitCommand(self, cmd, cwd=cwd, command = GitCommand(self, cmd, cwd=cwd,
@ -2156,6 +2204,8 @@ class Project(object):
if prune: if prune:
cmd.append('--prune') cmd.append('--prune')
# Always pass something for --recurse-submodules, git with GIT_DIR behaves
# incorrectly when not given `--recurse-submodules=no`. (b/218891912)
cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}') cmd.append(f'--recurse-submodules={"on-demand" if submodules else "no"}')
spec = [] spec = []
@ -2287,7 +2337,7 @@ class Project(object):
if initial and (self.manifest.manifestProject.depth or self.clone_depth): if initial and (self.manifest.manifestProject.depth or self.clone_depth):
return False return False
remote = self.GetRemote(self.remote.name) remote = self.GetRemote()
bundle_url = remote.url + '/clone.bundle' bundle_url = remote.url + '/clone.bundle'
bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url) bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
if GetSchemeFromUrl(bundle_url) not in ('http', 'https', if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
@ -2592,7 +2642,7 @@ class Project(object):
if not filecmp.cmp(stock_hook, dst, shallow=False): if not filecmp.cmp(stock_hook, dst, shallow=False):
if not quiet: if not quiet:
_warn("%s: Not replacing locally modified %s hook", _warn("%s: Not replacing locally modified %s hook",
self.relpath, name) self.RelPath(local=False), name)
continue continue
try: try:
platform_utils.symlink( platform_utils.symlink(
@ -2608,7 +2658,7 @@ class Project(object):
def _InitRemote(self): def _InitRemote(self):
if self.remote.url: if self.remote.url:
remote = self.GetRemote(self.remote.name) remote = self.GetRemote()
remote.url = self.remote.url remote.url = self.remote.url
remote.pushUrl = self.remote.pushUrl remote.pushUrl = self.remote.pushUrl
remote.review = self.remote.review remote.review = self.remote.review
@ -2621,6 +2671,7 @@ class Project(object):
remote.Save() remote.Save()
def _InitMRef(self): def _InitMRef(self):
"""Initialize the pseudo m/<manifest branch> ref."""
if self.manifest.branch: if self.manifest.branch:
if self.use_git_worktrees: if self.use_git_worktrees:
# Set up the m/ space to point to the worktree-specific ref space. # Set up the m/ space to point to the worktree-specific ref space.
@ -2650,6 +2701,16 @@ class Project(object):
self._InitAnyMRef(HEAD, self.bare_git) self._InitAnyMRef(HEAD, self.bare_git)
def _InitAnyMRef(self, ref, active_git, detach=False): def _InitAnyMRef(self, ref, active_git, detach=False):
"""Initialize |ref| in |active_git| to the value in the manifest.
This points |ref| to the <project> setting in the manifest.
Args:
ref: The branch to update.
active_git: The git repository to make updates in.
detach: Whether to update target of symbolic refs, or overwrite the ref
directly (and thus make it non-symbolic).
"""
cur = self.bare_ref.symref(ref) cur = self.bare_ref.symref(ref)
if self.revisionId: if self.revisionId:
@ -2658,7 +2719,7 @@ class Project(object):
dst = self.revisionId + '^0' dst = self.revisionId + '^0'
active_git.UpdateRef(ref, dst, message=msg, detach=True) active_git.UpdateRef(ref, dst, message=msg, detach=True)
else: else:
remote = self.GetRemote(self.remote.name) remote = self.GetRemote()
dst = remote.ToLocal(self.revisionExpr) dst = remote.ToLocal(self.revisionExpr)
if cur != dst: if cur != dst:
msg = 'manifest set to %s' % self.revisionExpr msg = 'manifest set to %s' % self.revisionExpr
@ -2687,7 +2748,7 @@ class Project(object):
'work tree. If you\'re comfortable with the ' 'work tree. If you\'re comfortable with the '
'possibility of losing the work tree\'s git metadata,' 'possibility of losing the work tree\'s git metadata,'
' use `repo sync --force-sync {0}` to ' ' use `repo sync --force-sync {0}` to '
'proceed.'.format(self.relpath)) 'proceed.'.format(self.RelPath(local=False)))
def _ReferenceGitDir(self, gitdir, dotgit, copy_all): def _ReferenceGitDir(self, gitdir, dotgit, copy_all):
"""Update |dotgit| to reference |gitdir|, using symlinks where possible. """Update |dotgit| to reference |gitdir|, using symlinks where possible.
@ -2733,6 +2794,35 @@ class Project(object):
else: else:
raise raise
def _InitialCheckoutStart(self):
"""Called when checking out a project for the first time.
This will use temporary non-visible paths so we can be safely interrupted
without leaving incomplete state behind.
"""
paths = [f'{x}.tmp' for x in (self.relpath, self.worktree, self.gitdir, self.objdir)]
for p in paths:
platform_utils.rmtree(p, ignore_errors=True)
self.UpdatePaths(*paths)
def _InitialCheckoutFinalizeNetworkHalf(self):
"""Finalize the object dirs after network syncing works."""
# Once the network half finishes, we can move the objects into the right
# place by removing the ".tmp" suffix on the dirs.
platform_utils.rmtree(self.gitdir[:-4], ignore_errors=True)
os.rename(self.gitdir, self.gitdir[:-4])
self.UpdatePaths(self.relpath, self.worktree, self.gitdir[:-4], self.objdir[:-4])
def _InitialCheckoutFinalizeLocalHalf(self):
"""Finalize the initial checkout and make it available."""
assert self.gitdir == self.objdir
# Once the local half finishes, we can move the manifest dir into the right
# place by removing the ".tmp" suffix on the dirs.
platform_utils.rmtree(self.worktree[:-4], ignore_errors=True)
os.rename(self.worktree, self.worktree[:-4])
self.UpdatePaths(
self.relpath[:-4], self.worktree[:-4], self.gitdir, self.objdir)
def _InitGitWorktree(self): def _InitGitWorktree(self):
"""Init the project using git worktrees.""" """Init the project using git worktrees."""
self.bare_git.worktree('prune') self.bare_git.worktree('prune')
@ -3167,7 +3257,7 @@ class _InfoMessage(object):
self.text = text self.text = text
def Print(self, syncbuf): def Print(self, syncbuf):
syncbuf.out.info('%s/: %s', self.project.relpath, self.text) syncbuf.out.info('%s/: %s', self.project.RelPath(local=False), self.text)
syncbuf.out.nl() syncbuf.out.nl()
@ -3179,7 +3269,7 @@ class _Failure(object):
def Print(self, syncbuf): def Print(self, syncbuf):
syncbuf.out.fail('error: %s/: %s', syncbuf.out.fail('error: %s/: %s',
self.project.relpath, self.project.RelPath(local=False),
str(self.why)) str(self.why))
syncbuf.out.nl() syncbuf.out.nl()
@ -3192,7 +3282,7 @@ class _Later(object):
def Run(self, syncbuf): def Run(self, syncbuf):
out = syncbuf.out out = syncbuf.out
out.project('project %s/', self.project.relpath) out.project('project %s/', self.project.RelPath(local=False))
out.nl() out.nl()
try: try:
self.action() self.action()
@ -3443,6 +3533,67 @@ class ManifestProject(MetaProject):
"""Return the name of the platform.""" """Return the name of the platform."""
return platform.system().lower() return platform.system().lower()
def SyncWithPossibleInit(self, submanifest, verbose=False,
current_branch_only=False, tags='', git_event_log=None):
"""Sync a manifestProject, possibly for the first time.
Call Sync() with arguments from the most recent `repo init`. If this is a
new sub manifest, then inherit options from the parent's manifestProject.
This is used by subcmds.Sync() to do an initial download of new sub
manifests.
Args:
submanifest: an XmlSubmanifest, the submanifest to re-sync.
verbose: a boolean, whether to show all output, rather than only errors.
current_branch_only: a boolean, whether to only fetch the current manifest
branch from the server.
tags: a boolean, whether to fetch tags.
git_event_log: an EventLog, for git tracing.
"""
# TODO(lamontjones): when refactoring sync (and init?) consider how to
# better get the init options that we should use for new submanifests that
# are added when syncing an existing workspace.
git_event_log = git_event_log or EventLog()
spec = submanifest.ToSubmanifestSpec()
# Use the init options from the existing manifestProject, or the parent if
# it doesn't exist.
#
# Today, we only support changing manifest_groups on the sub-manifest, with
# no supported-for-the-user way to change the other arguments from those
# specified by the outermost manifest.
#
# TODO(lamontjones): determine which of these should come from the outermost
# manifest and which should come from the parent manifest.
mp = self if self.Exists else submanifest.parent.manifestProject
return self.Sync(
manifest_url=spec.manifestUrl,
manifest_branch=spec.revision,
standalone_manifest=mp.standalone_manifest_url,
groups=mp.manifest_groups,
platform=mp.manifest_platform,
mirror=mp.mirror,
dissociate=mp.dissociate,
reference=mp.reference,
worktree=mp.use_worktree,
submodules=mp.submodules,
archive=mp.archive,
partial_clone=mp.partial_clone,
clone_filter=mp.clone_filter,
partial_clone_exclude=mp.partial_clone_exclude,
clone_bundle=mp.clone_bundle,
git_lfs=mp.git_lfs,
use_superproject=mp.use_superproject,
verbose=verbose,
current_branch_only=current_branch_only,
tags=tags,
depth=mp.depth,
git_event_log=git_event_log,
manifest_name=spec.manifestName,
this_manifest_only=True,
outer_manifest=False,
)
def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None, def Sync(self, _kwargs_only=(), manifest_url='', manifest_branch=None,
standalone_manifest=False, groups='', mirror=False, reference='', standalone_manifest=False, groups='', mirror=False, reference='',
dissociate=False, worktree=False, submodules=False, archive=False, dissociate=False, worktree=False, submodules=False, archive=False,
@ -3495,7 +3646,7 @@ class ManifestProject(MetaProject):
""" """
assert _kwargs_only == (), 'Sync only accepts keyword arguments.' assert _kwargs_only == (), 'Sync only accepts keyword arguments.'
groups = groups or 'default' groups = groups or self.manifest.GetDefaultGroupsStr(with_platform=False)
platform = platform or 'auto' platform = platform or 'auto'
git_event_log = git_event_log or EventLog() git_event_log = git_event_log or EventLog()
if outer_manifest and self.manifest.is_submanifest: if outer_manifest and self.manifest.is_submanifest:
@ -3558,6 +3709,8 @@ class ManifestProject(MetaProject):
(GitConfig.ForUser().UrlInsteadOf(manifest_url),), (GitConfig.ForUser().UrlInsteadOf(manifest_url),),
file=sys.stderr) file=sys.stderr)
self._InitialCheckoutStart()
# The manifest project object doesn't keep track of the path on the # 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. # server where this git is located, so let's save that here.
mirrored_manifest_git = None mirrored_manifest_git = None
@ -3589,7 +3742,7 @@ class ManifestProject(MetaProject):
# Set the remote URL before the remote branch as we might need it below. # Set the remote URL before the remote branch as we might need it below.
if manifest_url: if manifest_url:
r = self.GetRemote(self.remote.name) r = self.GetRemote()
r.url = manifest_url r.url = manifest_url
r.ResetFetch() r.ResetFetch()
r.Save() r.Save()
@ -3709,46 +3862,46 @@ class ManifestProject(MetaProject):
if use_superproject is not None: if use_superproject is not None:
self.config.SetBoolean('repo.superproject', use_superproject) self.config.SetBoolean('repo.superproject', use_superproject)
if standalone_manifest: if not standalone_manifest:
if is_new: if not self.Sync_NetworkHalf(
manifest_name = 'default.xml' is_new=is_new, quiet=not verbose, verbose=verbose,
manifest_data = fetch.fetch_file(manifest_url, verbose=verbose) clone_bundle=clone_bundle, current_branch_only=current_branch_only,
dest = os.path.join(self.worktree, manifest_name) tags=tags, submodules=submodules, clone_filter=clone_filter,
os.makedirs(os.path.dirname(dest), exist_ok=True) partial_clone_exclude=self.manifest.PartialCloneExclude):
with open(dest, 'wb') as f: r = self.GetRemote()
f.write(manifest_data) print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
return
if not self.Sync_NetworkHalf(is_new=is_new, quiet=not verbose, verbose=verbose,
clone_bundle=clone_bundle,
current_branch_only=current_branch_only,
tags=tags, submodules=submodules,
clone_filter=clone_filter,
partial_clone_exclude=self.manifest.PartialCloneExclude):
r = self.GetRemote(self.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:
platform_utils.rmtree(self.gitdir)
return False
if manifest_branch:
self.MetaBranchSwitch(submodules=submodules)
syncbuf = SyncBuffer(self.config)
self.Sync_LocalHalf(syncbuf, submodules=submodules)
syncbuf.Finish()
if is_new or self.CurrentBranch is None:
if not self.StartBranch('default'):
print('fatal: cannot create default in manifest', file=sys.stderr)
return False return False
if not manifest_name: if manifest_branch:
print('fatal: manifest name (-m) is required.', file=sys.stderr) self.MetaBranchSwitch(submodules=submodules)
return False
if is_new:
self._InitialCheckoutFinalizeNetworkHalf()
syncbuf = SyncBuffer(self.config)
self.Sync_LocalHalf(syncbuf, submodules=submodules)
syncbuf.Finish()
if is_new or self.CurrentBranch is None:
if not self.StartBranch('default'):
print('fatal: cannot create default in manifest', file=sys.stderr)
return False
if not manifest_name:
print('fatal: manifest name (-m) is required.', file=sys.stderr)
return False
elif is_new:
# This is a new standalone manifest.
manifest_name = 'default.xml'
manifest_data = fetch.fetch_file(manifest_url, verbose=verbose)
dest = os.path.join(self.worktree, manifest_name)
os.makedirs(os.path.dirname(dest), exist_ok=True)
with open(dest, 'wb') as f:
f.write(manifest_data)
if is_new:
self._InitialCheckoutFinalizeLocalHalf()
try: try:
self.manifest.Link(manifest_name) self.manifest.Link(manifest_name)
@ -3789,10 +3942,10 @@ class ManifestProject(MetaProject):
outer_manifest=False, outer_manifest=False,
) )
# Lastly, clone the superproject(s). # Lastly, if the manifest has a <superproject> then have the superproject
if self.manifest.manifestProject.use_superproject: # sync it (if it will be used).
sync_result = Superproject( if git_superproject.UseSuperproject(use_superproject, self.manifest):
self.manifest, self.manifest.repodir, git_event_log, quiet=not verbose).Sync() sync_result = self.manifest.superproject.Sync(git_event_log)
if not sync_result.success: if not sync_result.success:
print('warning: git update of superproject for ' print('warning: git update of superproject for '
f'{self.manifest.path_prefix} failed, repo sync will not use ' f'{self.manifest.path_prefix} failed, repo sync will not use '

21
repo
View File

@ -149,7 +149,7 @@ if not REPO_REV:
BUG_URL = 'https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue' BUG_URL = 'https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue'
# increment this whenever we make important changes to this script # increment this whenever we make important changes to this script
VERSION = (2, 21) VERSION = (2, 29)
# increment this if the MAINTAINER_KEYS block is modified # increment this if the MAINTAINER_KEYS block is modified
KEYRING_VERSION = (2, 3) KEYRING_VERSION = (2, 3)
@ -316,6 +316,9 @@ def InitParser(parser, gitc_init=False):
help='download the manifest as a static file ' help='download the manifest as a static file '
'rather then create a git checkout of ' 'rather then create a git checkout of '
'the manifest repo') 'the manifest repo')
group.add_option('--manifest-depth', type='int', default=1, metavar='DEPTH',
help='create a shallow clone of the manifest repo with '
'given depth; see git clone (default: %default)')
# Options that only affect manifest project, and not any of the projects # Options that only affect manifest project, and not any of the projects
# specified in the manifest itself. # specified in the manifest itself.
@ -325,9 +328,9 @@ def InitParser(parser, gitc_init=False):
# want -c, so try to satisfy both as best we can. # want -c, so try to satisfy both as best we can.
if not gitc_init: if not gitc_init:
cbr_opts += ['-c'] cbr_opts += ['-c']
group.add_option(*cbr_opts, group.add_option(*cbr_opts, default=True,
dest='current_branch_only', action='store_true', dest='current_branch_only', action='store_true',
help='fetch only current manifest branch from server') help='fetch only current manifest branch from server (default)')
group.add_option('--no-current-branch', group.add_option('--no-current-branch',
dest='current_branch_only', action='store_false', dest='current_branch_only', action='store_false',
help='fetch all manifest branches from server') help='fetch all manifest branches from server')
@ -612,15 +615,20 @@ def _Init(args, gitc_init=False):
try: try:
if not opt.quiet: if not opt.quiet:
print('Downloading Repo source from', url) print('Downloading Repo source from', url)
dst = os.path.abspath(os.path.join(repodir, S_repo)) dst_final = os.path.abspath(os.path.join(repodir, S_repo))
dst = dst_final + '.tmp'
shutil.rmtree(dst, ignore_errors=True)
_Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose) _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose)
remote_ref, rev = check_repo_rev(dst, rev, opt.repo_verify, quiet=opt.quiet) remote_ref, rev = check_repo_rev(dst, rev, opt.repo_verify, quiet=opt.quiet)
_Checkout(dst, remote_ref, rev, opt.quiet) _Checkout(dst, remote_ref, rev, opt.quiet)
if not os.path.isfile(os.path.join(dst, 'repo')): if not os.path.isfile(os.path.join(dst, 'repo')):
print("warning: '%s' does not look like a git-repo repository, is " print("fatal: '%s' does not look like a git-repo repository, is "
"REPO_URL set correctly?" % url, file=sys.stderr) "--repo-url set correctly?" % url, file=sys.stderr)
raise CloneFailure()
os.rename(dst, dst_final)
except CloneFailure: except CloneFailure:
print('fatal: double check your --repo-rev setting.', file=sys.stderr) print('fatal: double check your --repo-rev setting.', file=sys.stderr)
@ -1317,6 +1325,7 @@ def main(orig_args):
print("fatal: cloning the git-repo repository failed, will remove " print("fatal: cloning the git-repo repository failed, will remove "
"'%s' " % path, file=sys.stderr) "'%s' " % path, file=sys.stderr)
shutil.rmtree(path, ignore_errors=True) shutil.rmtree(path, ignore_errors=True)
shutil.rmtree(path + '.tmp', ignore_errors=True)
sys.exit(1) sys.exit(1)
repo_main, rel_repo_dir = _FindRepo() repo_main, rel_repo_dir = _FindRepo()
else: else:

View File

@ -60,8 +60,10 @@ change id will be added.
capture_stderr=True) capture_stderr=True)
status = p.Wait() status = p.Wait()
print(p.stdout, file=sys.stdout) if p.stdout:
print(p.stderr, file=sys.stderr) print(p.stdout.strip(), file=sys.stdout)
if p.stderr:
print(p.stderr.strip(), file=sys.stderr)
if status == 0: if status == 0:
# The cherry-pick was applied correctly. We just need to edit the # The cherry-pick was applied correctly. We just need to edit the

View File

@ -35,18 +35,21 @@ to the Unix 'patch' command.
dest='absolute', action='store_true', 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, project): def _ExecuteOne(self, absolute, local, project):
"""Obtains the diff for a specific project. """Obtains the diff for a specific project.
Args: Args:
absolute: Paths are relative to the root. 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. project: Project to get status of.
Returns: Returns:
The status of the project. The status of the project.
""" """
buf = io.StringIO() buf = io.StringIO()
ret = project.PrintWorkTreeDiff(absolute, output_redir=buf) ret = project.PrintWorkTreeDiff(absolute, output_redir=buf, local=local)
return (ret, buf.getvalue()) return (ret, buf.getvalue())
def Execute(self, opt, args): def Execute(self, opt, args):
@ -63,7 +66,7 @@ to the Unix 'patch' command.
return self.ExecuteInParallel( return self.ExecuteInParallel(
opt.jobs, opt.jobs,
functools.partial(self._ExecuteOne, opt.absolute), functools.partial(self._ExecuteOne, opt.absolute, opt.this_manifest_only),
all_projects, all_projects,
callback=_ProcessResults, callback=_ProcessResults,
ordered=True) ordered=True)

View File

@ -65,8 +65,7 @@ class Info(PagedCommand):
self.manifest = self.manifest.outer_client self.manifest = self.manifest.outer_client
manifestConfig = self.manifest.manifestProject.config manifestConfig = self.manifest.manifestProject.config
mergeBranch = manifestConfig.GetBranch("default").merge mergeBranch = manifestConfig.GetBranch("default").merge
manifestGroups = (manifestConfig.GetString('manifest.groups') manifestGroups = self.manifest.GetGroupsStr()
or 'all,-notdefault')
self.heading("Manifest branch: ") self.heading("Manifest branch: ")
if self.manifest.default.revisionExpr: if self.manifest.default.revisionExpr:

View File

@ -24,8 +24,6 @@ from error import ManifestParseError
from project import SyncBuffer from project import SyncBuffer
from git_config import GitConfig from git_config import GitConfig
from git_command import git_require, MIN_GIT_VERSION_SOFT, MIN_GIT_VERSION_HARD from git_command import git_require, MIN_GIT_VERSION_SOFT, MIN_GIT_VERSION_HARD
import fetch
import platform_utils
from wrapper import Wrapper from wrapper import Wrapper
@ -91,11 +89,10 @@ to update the working directory files.
def _Options(self, p, gitc_init=False): def _Options(self, p, gitc_init=False):
Wrapper().InitParser(p, gitc_init=gitc_init) Wrapper().InitParser(p, gitc_init=gitc_init)
m = p.add_option_group('Multi-manifest') m = p.add_option_group('Multi-manifest')
m.add_option('--outer-manifest', action='store_true', m.add_option('--outer-manifest', action='store_true', default=True,
help='operate starting at the outermost manifest') help='operate starting at the outermost manifest')
m.add_option('--no-outer-manifest', dest='outer_manifest', m.add_option('--no-outer-manifest', dest='outer_manifest',
action='store_false', default=None, action='store_false', help='do not operate on outer manifests')
help='do not operate on outer manifests')
m.add_option('--this-manifest-only', action='store_true', default=None, m.add_option('--this-manifest-only', action='store_true', default=None,
help='only operate on this (sub)manifest') help='only operate on this (sub)manifest')
m.add_option('--no-this-manifest-only', '--all-manifests', m.add_option('--no-this-manifest-only', '--all-manifests',
@ -112,6 +109,10 @@ to update the working directory files.
Args: Args:
opt: options from optparse. 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( if not self.manifest.manifestProject.Sync(
manifest_url=opt.manifest_url, manifest_url=opt.manifest_url,
manifest_branch=opt.manifest_branch, manifest_branch=opt.manifest_branch,
@ -147,7 +148,7 @@ to update the working directory files.
return value return value
return a return a
def _ShouldConfigureUser(self, opt): def _ShouldConfigureUser(self, opt, existing_checkout):
gc = self.client.globalConfig gc = self.client.globalConfig
mp = self.manifest.manifestProject mp = self.manifest.manifestProject
@ -159,7 +160,7 @@ to update the working directory files.
mp.config.SetString('user.name', gc.GetString('user.name')) mp.config.SetString('user.name', gc.GetString('user.name'))
mp.config.SetString('user.email', gc.GetString('user.email')) mp.config.SetString('user.email', gc.GetString('user.email'))
if not opt.quiet: if not opt.quiet and not existing_checkout or opt.verbose:
print() print()
print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'), print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
mp.config.GetString('user.email'))) mp.config.GetString('user.email')))
@ -244,7 +245,7 @@ to update the working directory files.
if current_dir != self.manifest.topdir: if current_dir != self.manifest.topdir:
print('If this is not the directory in which you want to initialize ' print('If this is not the directory in which you want to initialize '
'repo, please run:') '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.') print('and try again.')
def ValidateOptions(self, opt, args): def ValidateOptions(self, opt, args):
@ -314,10 +315,17 @@ to update the working directory files.
# Older versions of git supported worktree, but had dangerous gc bugs. # Older versions of git supported worktree, but had dangerous gc bugs.
git_require((2, 15, 0), fail=True, msg='git gc worktree corruption') 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._SyncManifest(opt)
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror: if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
if opt.config_name or self._ShouldConfigureUser(opt): if opt.config_name or self._ShouldConfigureUser(opt, existing_checkout):
self._ConfigureUser(opt) self._ConfigureUser(opt)
self._ConfigureColor() self._ConfigureColor()

View File

@ -75,6 +75,7 @@ The '%prog' command stages files to prepare the next commit.
out.nl() out.nl()
out.prompt('project> ') out.prompt('project> ')
out.flush()
try: try:
a = sys.stdin.readline() a = sys.stdin.readline()
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -83,7 +83,7 @@ the following meanings:
dest='orphans', action='store_true', dest='orphans', action='store_true',
help="include objects in working directory outside of repo projects") help="include objects in working directory outside of repo projects")
def _StatusHelper(self, quiet, project): def _StatusHelper(self, quiet, local, project):
"""Obtains the status for a specific project. """Obtains the status for a specific project.
Obtains the status for a project, redirecting the output to Obtains the status for a project, redirecting the output to
@ -91,13 +91,17 @@ the following meanings:
Args: Args:
quiet: Where to output the status. 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. project: Project to get status of.
Returns: Returns:
The status of the project. The status of the project.
""" """
buf = io.StringIO() buf = io.StringIO()
ret = project.PrintWorkTreeStatus(quiet=quiet, output_redir=buf) ret = project.PrintWorkTreeStatus(quiet=quiet, output_redir=buf,
local=local)
return (ret, buf.getvalue()) return (ret, buf.getvalue())
def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring): def _FindOrphans(self, dirs, proj_dirs, proj_dirs_parents, outstring):
@ -130,7 +134,7 @@ the following meanings:
counter = self.ExecuteInParallel( counter = self.ExecuteInParallel(
opt.jobs, opt.jobs,
functools.partial(self._StatusHelper, opt.quiet), functools.partial(self._StatusHelper, opt.quiet, opt.this_manifest_only),
all_projects, all_projects,
callback=_ProcessResults, callback=_ProcessResults,
ordered=True) ordered=True)

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import collections
import functools import functools
import http.cookiejar as cookielib import http.cookiejar as cookielib
import io import io
@ -27,6 +28,7 @@ import time
import urllib.error import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import xml.parsers.expat
import xmlrpc.client import xmlrpc.client
try: try:
@ -51,7 +53,7 @@ import git_superproject
import gitc_utils import gitc_utils
from project import Project from project import Project
from project import RemoteSpec from project import RemoteSpec
from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE from command import Command, DEFAULT_LOCAL_JOBS, MirrorSafeCommand, WORKER_BATCH_SIZE
from error import RepoChangedException, GitError, ManifestParseError from error import RepoChangedException, GitError, ManifestParseError
import platform_utils import platform_utils
from project import SyncBuffer from project import SyncBuffer
@ -64,9 +66,8 @@ _ONE_DAY_S = 24 * 60 * 60
class Sync(Command, MirrorSafeCommand): class Sync(Command, MirrorSafeCommand):
jobs = 1
COMMON = True COMMON = True
MULTI_MANIFEST_SUPPORT = False MULTI_MANIFEST_SUPPORT = True
helpSummary = "Update working tree to the latest revision" helpSummary = "Update working tree to the latest revision"
helpUsage = """ helpUsage = """
%prog [<project>...] %prog [<project>...]
@ -167,21 +168,16 @@ If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
later is required to fix a server side protocol bug. later is required to fix a server side protocol bug.
""" """
PARALLEL_JOBS = 1 # A value of 0 means we want parallel jobs, but we'll determine the default
# value later on.
def _CommonOptions(self, p): PARALLEL_JOBS = 0
if self.outer_client and self.outer_client.manifest:
try:
self.PARALLEL_JOBS = self.outer_client.manifest.default.sync_j
except ManifestParseError:
pass
super()._CommonOptions(p)
def _Options(self, p, show_smart=True): def _Options(self, p, show_smart=True):
p.add_option('--jobs-network', default=None, type=int, metavar='JOBS', p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
help='number of network jobs to run in parallel (defaults to --jobs)') help='number of network jobs to run in parallel (defaults to --jobs or 1)')
p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS', p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
help='number of local checkout jobs to run in parallel (defaults to --jobs)') help='number of local checkout jobs to run in parallel (defaults to --jobs or '
f'{DEFAULT_LOCAL_JOBS})')
p.add_option('-f', '--force-broken', p.add_option('-f', '--force-broken',
dest='force_broken', action='store_true', dest='force_broken', action='store_true',
@ -295,52 +291,92 @@ later is required to fix a server side protocol bug.
""" """
return git_superproject.UseSuperproject(opt.use_superproject, manifest) or opt.current_branch_only return git_superproject.UseSuperproject(opt.use_superproject, manifest) or opt.current_branch_only
def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data, manifest): def _UpdateProjectsRevisionId(self, opt, args, superproject_logging_data,
"""Update revisionId of every project with the SHA from superproject. manifest):
"""Update revisionId of projects with the commit hash from the superproject.
This function updates each project's revisionId with SHA from superproject. This function updates each project's revisionId with the commit hash from
It writes the updated manifest into a file and reloads the manifest from it. the superproject. It writes the updated manifest into a file and reloads
the manifest from it. When appropriate, sub manifests are also processed.
Args: Args:
opt: Program options returned from optparse. See _Options(). opt: Program options returned from optparse. See _Options().
args: Arguments to pass to GetProjects. See the GetProjects args: Arguments to pass to GetProjects. See the GetProjects
docstring for details. docstring for details.
load_local_manifests: Whether to load local manifests. superproject_logging_data: A dictionary of superproject data to log.
superproject_logging_data: A dictionary of superproject data that is to be logged.
manifest: The manifest to use. manifest: The manifest to use.
Returns:
Returns path to the overriding manifest file instead of None.
""" """
superproject = self.manifest.superproject have_superproject = manifest.superproject or any(
superproject.SetQuiet(opt.quiet) m.superproject for m in manifest.all_children)
print_messages = git_superproject.PrintMessages(opt.use_superproject, if not have_superproject:
self.manifest) return
superproject.SetPrintMessages(print_messages)
if opt.local_only: if opt.local_only and manifest.superproject:
manifest_path = superproject.manifest_path manifest_path = manifest.superproject.manifest_path
if manifest_path: if manifest_path:
self._ReloadManifest(manifest_path, manifest, load_local_manifests) self._ReloadManifest(manifest_path, manifest)
return manifest_path return
all_projects = self.GetProjects(args, all_projects = self.GetProjects(args,
missing_ok=True, missing_ok=True,
submodules_ok=opt.fetch_submodules) submodules_ok=opt.fetch_submodules,
update_result = superproject.UpdateProjectsRevisionId( manifest=manifest,
all_projects, git_event_log=self.git_event_log) all_manifests=not opt.this_manifest_only)
manifest_path = update_result.manifest_path
superproject_logging_data['updatedrevisionid'] = bool(manifest_path) per_manifest = collections.defaultdict(list)
if manifest_path: manifest_paths = {}
self._ReloadManifest(manifest_path, manifest, load_local_manifests) if opt.this_manifest_only:
per_manifest[manifest.path_prefix] = all_projects
else: else:
if print_messages: for p in all_projects:
print('warning: Update of revisionId from superproject has failed, ' per_manifest[p.manifest.path_prefix].append(p)
'repo sync will not use superproject to fetch the source. ',
'Please resync with the --no-use-superproject option to avoid this repo warning.', superproject_logging_data = {}
file=sys.stderr) need_unload = False
if update_result.fatal and opt.use_superproject is not None: for m in self.ManifestList(opt):
sys.exit(1) if not m.path_prefix in per_manifest:
return manifest_path continue
use_super = git_superproject.UseSuperproject(opt.use_superproject, m)
if superproject_logging_data:
superproject_logging_data['multimanifest'] = True
superproject_logging_data.update(
superproject=use_super,
haslocalmanifests=bool(m.HasLocalManifests),
hassuperprojecttag=bool(m.superproject),
)
if use_super and (m.IsMirror or m.IsArchive):
# Don't use superproject, because we have no working tree.
use_super = False
superproject_logging_data['superproject'] = False
superproject_logging_data['noworktree'] = True
if opt.use_superproject is not False:
print(f'{m.path_prefix}: not using superproject because there is no '
'working tree.')
if not use_super:
continue
m.superproject.SetQuiet(opt.quiet)
print_messages = git_superproject.PrintMessages(opt.use_superproject, m)
m.superproject.SetPrintMessages(print_messages)
update_result = m.superproject.UpdateProjectsRevisionId(
per_manifest[m.path_prefix], git_event_log=self.git_event_log)
manifest_path = update_result.manifest_path
superproject_logging_data['updatedrevisionid'] = bool(manifest_path)
if manifest_path:
m.SetManifestOverride(manifest_path)
need_unload = True
else:
if print_messages:
print(f'{m.path_prefix}: 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.',
file=sys.stderr)
if update_result.fatal and opt.use_superproject is not None:
sys.exit(1)
if need_unload:
m.outer_client.manifest.Unload()
def _FetchProjectList(self, opt, projects): def _FetchProjectList(self, opt, projects):
"""Main function of the fetch worker. """Main function of the fetch worker.
@ -410,7 +446,7 @@ later is required to fix a server side protocol bug.
def _Fetch(self, projects, opt, err_event, ssh_proxy): def _Fetch(self, projects, opt, err_event, ssh_proxy):
ret = True ret = True
jobs = opt.jobs_network if opt.jobs_network else self.jobs jobs = opt.jobs_network
fetched = set() fetched = set()
pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet) pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
@ -485,8 +521,8 @@ later is required to fix a server side protocol bug.
return (ret, fetched) return (ret, fetched)
def _FetchMain(self, opt, args, all_projects, err_event, manifest_name, def _FetchMain(self, opt, args, all_projects, err_event,
load_local_manifests, ssh_proxy, manifest): ssh_proxy, manifest):
"""The main network fetch loop. """The main network fetch loop.
Args: Args:
@ -494,8 +530,6 @@ later is required to fix a server side protocol bug.
args: Command line args used to filter out projects. args: Command line args used to filter out projects.
all_projects: List of all projects that should be fetched. all_projects: List of all projects that should be fetched.
err_event: Whether an error was hit while processing. err_event: Whether an error was hit while processing.
manifest_name: Manifest file to be reloaded.
load_local_manifests: Whether to load local manifests.
ssh_proxy: SSH manager for clients & masters. ssh_proxy: SSH manager for clients & masters.
manifest: The manifest to use. manifest: The manifest to use.
@ -526,10 +560,12 @@ later is required to fix a server side protocol bug.
# Iteratively fetch missing and/or nested unregistered submodules # Iteratively fetch missing and/or nested unregistered submodules
previously_missing_set = set() previously_missing_set = set()
while True: while True:
self._ReloadManifest(manifest_name, self.manifest, load_local_manifests) self._ReloadManifest(None, manifest)
all_projects = self.GetProjects(args, all_projects = self.GetProjects(args,
missing_ok=True, missing_ok=True,
submodules_ok=opt.fetch_submodules) submodules_ok=opt.fetch_submodules,
manifest=manifest,
all_manifests=not opt.this_manifest_only)
missing = [] missing = []
for project in all_projects: for project in all_projects:
if project.gitdir not in fetched: if project.gitdir not in fetched:
@ -610,7 +646,7 @@ later is required to fix a server side protocol bug.
return ret return ret
return self.ExecuteInParallel( return self.ExecuteInParallel(
opt.jobs_checkout if opt.jobs_checkout else self.jobs, opt.jobs_checkout,
functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync), functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
all_projects, all_projects,
callback=_ProcessResults, callback=_ProcessResults,
@ -622,21 +658,27 @@ later is required to fix a server side protocol bug.
tidy_dirs = {} tidy_dirs = {}
for project in projects: for project in projects:
# Make sure pruning never kicks in with shared projects. # Make sure pruning never kicks in with shared projects that do not use
# alternates to avoid corruption.
if (not project.use_git_worktrees and if (not project.use_git_worktrees and
len(project.manifest.GetProjectsWithName(project.name)) > 1): len(project.manifest.GetProjectsWithName(project.name, all_manifests=True)) > 1):
if not opt.quiet: if project.UseAlternates:
print('\r%s: Shared project %s found, disabling pruning.' % # Undo logic set by previous versions of repo.
(project.relpath, project.name)) project.config.SetString('extensions.preciousObjects', None)
if git_require((2, 7, 0)): project.config.SetString('gc.pruneExpire', None)
project.EnableRepositoryExtension('preciousObjects')
else: else:
# This isn't perfect, but it's the best we can do with old git. if not opt.quiet:
print('\r%s: WARNING: shared projects are unreliable when using old ' print('\r%s: Shared project %s found, disabling pruning.' %
'versions of git; please upgrade to git-2.7.0+.' (project.relpath, project.name))
% (project.relpath,), if git_require((2, 7, 0)):
file=sys.stderr) project.EnableRepositoryExtension('preciousObjects')
project.config.SetString('gc.pruneExpire', 'never') else:
# This isn't perfect, but it's the best we can do with old git.
print('\r%s: WARNING: shared projects are unreliable when using old '
'versions of git; please upgrade to git-2.7.0+.'
% (project.relpath,),
file=sys.stderr)
project.config.SetString('gc.pruneExpire', 'never')
project.config.SetString('gc.autoDetach', 'false') project.config.SetString('gc.autoDetach', 'false')
# Only call git gc once per objdir, but call pack-refs for the remainder. # Only call git gc once per objdir, but call pack-refs for the remainder.
if project.objdir not in tidy_dirs: if project.objdir not in tidy_dirs:
@ -650,8 +692,7 @@ later is required to fix a server side protocol bug.
project.bare_git, project.bare_git,
) )
cpu_count = os.cpu_count() jobs = opt.jobs
jobs = min(self.jobs, cpu_count)
if jobs < 2: if jobs < 2:
for (run_gc, bare_git) in tidy_dirs.values(): for (run_gc, bare_git) in tidy_dirs.values():
@ -663,6 +704,7 @@ later is required to fix a server side protocol bug.
pm.end() pm.end()
return return
cpu_count = os.cpu_count()
config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1} config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
threads = set() threads = set()
@ -698,7 +740,7 @@ later is required to fix a server side protocol bug.
t.join() t.join()
pm.end() pm.end()
def _ReloadManifest(self, manifest_name, manifest, load_local_manifests=True): def _ReloadManifest(self, manifest_name, manifest):
"""Reload the manfiest from the file specified by the |manifest_name|. """Reload the manfiest from the file specified by the |manifest_name|.
It unloads the manifest if |manifest_name| is None. It unloads the manifest if |manifest_name| is None.
@ -706,17 +748,29 @@ later is required to fix a server side protocol bug.
Args: Args:
manifest_name: Manifest file to be reloaded. manifest_name: Manifest file to be reloaded.
manifest: The manifest to use. manifest: The manifest to use.
load_local_manifests: Whether to load local manifests.
""" """
if manifest_name: if manifest_name:
# Override calls Unload already # Override calls Unload already
manifest.Override(manifest_name, load_local_manifests=load_local_manifests) manifest.Override(manifest_name)
else: else:
manifest.Unload() manifest.Unload()
def UpdateProjectList(self, opt, manifest): def UpdateProjectList(self, opt, manifest):
"""Update the cached projects list for |manifest|
In a multi-manifest checkout, each manifest has its own project.list.
Args:
opt: Program options returned from optparse. See _Options().
manifest: The manifest to use.
Returns:
0: success
1: failure
"""
new_project_paths = [] new_project_paths = []
for project in self.GetProjects(None, missing_ok=True): for project in self.GetProjects(None, missing_ok=True, manifest=manifest,
all_manifests=False):
if project.relpath: if project.relpath:
new_project_paths.append(project.relpath) new_project_paths.append(project.relpath)
file_name = 'project.list' file_name = 'project.list'
@ -766,7 +820,8 @@ later is required to fix a server side protocol bug.
new_paths = {} new_paths = {}
new_linkfile_paths = [] new_linkfile_paths = []
new_copyfile_paths = [] new_copyfile_paths = []
for project in self.GetProjects(None, missing_ok=True): for project in self.GetProjects(None, missing_ok=True,
manifest=manifest, all_manifests=False):
new_linkfile_paths.extend(x.dest for x in project.linkfiles) new_linkfile_paths.extend(x.dest for x in project.linkfiles)
new_copyfile_paths.extend(x.dest for x in project.copyfiles) new_copyfile_paths.extend(x.dest for x in project.copyfiles)
@ -897,8 +952,40 @@ later is required to fix a server side protocol bug.
return manifest_name return manifest_name
def _UpdateAllManifestProjects(self, opt, mp, manifest_name):
"""Fetch & update the local manifest project.
After syncing the manifest project, if the manifest has any sub manifests,
those are recursively processed.
Args:
opt: Program options returned from optparse. See _Options().
mp: the manifestProject to query.
manifest_name: Manifest file to be reloaded.
"""
if not mp.standalone_manifest_url:
self._UpdateManifestProject(opt, mp, manifest_name)
if mp.manifest.submanifests:
for submanifest in mp.manifest.submanifests.values():
child = submanifest.repo_client.manifest
child.manifestProject.SyncWithPossibleInit(
submanifest,
current_branch_only=self._GetCurrentBranchOnly(opt, child),
verbose=opt.verbose,
tags=opt.tags,
git_event_log=self.git_event_log,
)
self._UpdateAllManifestProjects(opt, child.manifestProject, None)
def _UpdateManifestProject(self, opt, mp, manifest_name): def _UpdateManifestProject(self, opt, mp, manifest_name):
"""Fetch & update the local manifest project.""" """Fetch & update the local manifest project.
Args:
opt: Program options returned from optparse. See _Options().
mp: the manifestProject to query.
manifest_name: Manifest file to be reloaded.
"""
if not opt.local_only: if not opt.local_only:
start = time.time() start = time.time()
success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose, success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
@ -924,8 +1011,6 @@ later is required to fix a server side protocol bug.
if not clean: if not clean:
sys.exit(1) sys.exit(1)
self._ReloadManifest(manifest_name, mp.manifest) self._ReloadManifest(manifest_name, mp.manifest)
if opt.jobs is None:
self.jobs = mp.manifest.default.sync_j
def ValidateOptions(self, opt, args): def ValidateOptions(self, opt, args):
if opt.force_broken: if opt.force_broken:
@ -948,18 +1033,9 @@ later is required to fix a server side protocol bug.
if opt.prune is None: if opt.prune is None:
opt.prune = True opt.prune = True
if self.outer_client.manifest.is_multimanifest and not opt.this_manifest_only and args:
self.OptionParser.error('partial syncs must use --this-manifest-only')
def Execute(self, opt, args): def Execute(self, opt, args):
if opt.jobs:
self.jobs = opt.jobs
if self.jobs > 1:
soft_limit, _ = _rlimit_nofile()
self.jobs = min(self.jobs, (soft_limit - 5) // 3)
manifest = self.outer_manifest manifest = self.outer_manifest
if opt.this_manifest_only or not opt.outer_manifest: if not opt.outer_manifest:
manifest = self.manifest manifest = self.manifest
if opt.manifest_name: if opt.manifest_name:
@ -994,39 +1070,56 @@ later is required to fix a server side protocol bug.
'receive updates; run `repo init --repo-rev=stable` to fix.', 'receive updates; run `repo init --repo-rev=stable` to fix.',
file=sys.stderr) file=sys.stderr)
mp = manifest.manifestProject for m in self.ManifestList(opt):
is_standalone_manifest = bool(mp.standalone_manifest_url) mp = m.manifestProject
if not is_standalone_manifest: is_standalone_manifest = bool(mp.standalone_manifest_url)
mp.PreSync() if not is_standalone_manifest:
mp.PreSync()
if opt.repo_upgraded: if opt.repo_upgraded:
_PostRepoUpgrade(manifest, quiet=opt.quiet) _PostRepoUpgrade(m, quiet=opt.quiet)
if not opt.mp_update: if opt.mp_update:
self._UpdateAllManifestProjects(opt, mp, manifest_name)
else:
print('Skipping update of local manifest project.') print('Skipping update of local manifest project.')
elif not is_standalone_manifest:
self._UpdateManifestProject(opt, mp, manifest_name)
load_local_manifests = not manifest.HasLocalManifests # Now that the manifests are up-to-date, setup the jobs value.
use_superproject = git_superproject.UseSuperproject(opt.use_superproject, manifest) if opt.jobs is None:
if use_superproject and (manifest.IsMirror or manifest.IsArchive): # User has not made a choice, so use the manifest settings.
# Don't use superproject, because we have no working tree. opt.jobs = mp.default.sync_j
use_superproject = False if opt.jobs is not None:
if opt.use_superproject is not None: # Neither user nor manifest have made a choice.
print('Defaulting to no-use-superproject because there is no working tree.') if opt.jobs_network is None:
superproject_logging_data = { opt.jobs_network = opt.jobs
'superproject': use_superproject, if opt.jobs_checkout is None:
'haslocalmanifests': bool(manifest.HasLocalManifests), opt.jobs_checkout = opt.jobs
'hassuperprojecttag': bool(manifest.superproject), # Setup defaults if jobs==0.
} if not opt.jobs:
if use_superproject: if not opt.jobs_network:
manifest_name = self._UpdateProjectsRevisionId( opt.jobs_network = 1
opt, args, load_local_manifests, superproject_logging_data, if not opt.jobs_checkout:
manifest) or opt.manifest_name opt.jobs_checkout = DEFAULT_LOCAL_JOBS
opt.jobs = os.cpu_count()
# Try to stay under user rlimit settings.
#
# Since each worker requires at 3 file descriptors to run `git fetch`, use
# that to scale down the number of jobs. Unfortunately there isn't an easy
# way to determine this reliably as systems change, but it was last measured
# by hand in 2011.
soft_limit, _ = _rlimit_nofile()
jobs_soft_limit = max(1, (soft_limit - 5) // 3)
opt.jobs = min(opt.jobs, jobs_soft_limit)
opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
superproject_logging_data = {}
self._UpdateProjectsRevisionId(opt, args, superproject_logging_data,
manifest)
if self.gitc_manifest: if self.gitc_manifest:
gitc_manifest_projects = self.GetProjects(args, gitc_manifest_projects = self.GetProjects(args, missing_ok=True)
missing_ok=True)
gitc_projects = [] gitc_projects = []
opened_projects = [] opened_projects = []
for project in gitc_manifest_projects: for project in gitc_manifest_projects:
@ -1059,9 +1152,12 @@ later is required to fix a server side protocol bug.
for path in opened_projects] for path in opened_projects]
if not args: if not args:
return return
all_projects = self.GetProjects(args, all_projects = self.GetProjects(args,
missing_ok=True, missing_ok=True,
submodules_ok=opt.fetch_submodules) submodules_ok=opt.fetch_submodules,
manifest=manifest,
all_manifests=not opt.this_manifest_only)
err_network_sync = False err_network_sync = False
err_update_projects = False err_update_projects = False
@ -1073,7 +1169,6 @@ later is required to fix a server side protocol bug.
# Initialize the socket dir once in the parent. # Initialize the socket dir once in the parent.
ssh_proxy.sock() ssh_proxy.sock()
all_projects = self._FetchMain(opt, args, all_projects, err_event, all_projects = self._FetchMain(opt, args, all_projects, err_event,
manifest_name, load_local_manifests,
ssh_proxy, manifest) ssh_proxy, manifest)
if opt.network_only: if opt.network_only:
@ -1090,23 +1185,24 @@ later is required to fix a server side protocol bug.
file=sys.stderr) file=sys.stderr)
sys.exit(1) sys.exit(1)
if manifest.IsMirror or manifest.IsArchive: for m in self.ManifestList(opt):
# bail out now, we have no working tree if m.IsMirror or m.IsArchive:
return # bail out now, we have no working tree
continue
if self.UpdateProjectList(opt, manifest): if self.UpdateProjectList(opt, m):
err_event.set() err_event.set()
err_update_projects = True err_update_projects = True
if opt.fail_fast: if opt.fail_fast:
print('\nerror: Local checkouts *not* updated.', file=sys.stderr) print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
sys.exit(1) sys.exit(1)
err_update_linkfiles = not self.UpdateCopyLinkfileList(manifest) err_update_linkfiles = not self.UpdateCopyLinkfileList(m)
if err_update_linkfiles: if err_update_linkfiles:
err_event.set() err_event.set()
if opt.fail_fast: if opt.fail_fast:
print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr) print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
sys.exit(1) sys.exit(1)
err_results = [] err_results = []
# NB: We don't exit here because this is the last step. # NB: We don't exit here because this is the last step.
@ -1114,10 +1210,14 @@ later is required to fix a server side protocol bug.
if err_checkout: if err_checkout:
err_event.set() err_event.set()
# If there's a notice that's supposed to print at the end of the sync, print printed_notices = set()
# it now... # If there's a notice that's supposed to print at the end of the sync,
if manifest.notice: # print it now... But avoid printing duplicate messages, and preserve
print(manifest.notice) # order.
for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
if m.notice and m.notice not in printed_notices:
print(m.notice)
printed_notices.add(m.notice)
# If we saw an error, exit with code 1 so that other scripts can check. # If we saw an error, exit with code 1 so that other scripts can check.
if err_event.is_set(): if err_event.is_set():
@ -1332,11 +1432,16 @@ class PersistentTransport(xmlrpc.client.Transport):
raise raise
p, u = xmlrpc.client.getparser() p, u = xmlrpc.client.getparser()
while 1: # Response should be fairly small, so read it all at once.
data = response.read(1024) # This way we can show it to the user in case of error (e.g. HTML).
if not data: data = response.read()
break try:
p.feed(data) p.feed(data)
except xml.parsers.expat.ExpatError as e:
raise IOError(
f'Parsing the manifest failed: {e}\n'
f'Please report this to your manifest server admin.\n'
f'Here is the full response:\n{data.decode("utf-8")}')
p.close() p.close()
return u.close() return u.close()

View File

@ -78,6 +78,13 @@ added to the respective list of users, and emails are sent to any
new users. Users passed as --reviewers must already be registered new users. Users passed as --reviewers must already be registered
with the code review system, or the upload will fail. with the code review system, or the upload will fail.
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 # Configuration
review.URL.autoupload: review.URL.autoupload:
@ -190,6 +197,9 @@ Gerrit Code Review: https://www.gerritcodereview.com/
p.add_option('-w', '--wip', p.add_option('-w', '--wip',
action='store_true', dest='wip', default=False, action='store_true', dest='wip', default=False,
help='upload as a work-in-progress change') 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', p.add_option('-o', '--push-option',
type='string', action='append', dest='push_options', type='string', action='append', dest='push_options',
default=[], default=[],
@ -204,6 +214,12 @@ Gerrit Code Review: https://www.gerritcodereview.com/
p.add_option('-y', '--yes', p.add_option('-y', '--yes',
default=False, action='store_true', default=False, action='store_true',
help='answer yes to all safe prompts') 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', p.add_option('--no-cert-checks',
dest='validate_certs', action='store_false', default=True, dest='validate_certs', action='store_false', default=True,
help='disable verifying ssl certs (unsafe)') help='disable verifying ssl certs (unsafe)')
@ -246,7 +262,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
answer = sys.stdin.readline().strip().lower() answer = sys.stdin.readline().strip().lower()
answer = answer in ('y', 'yes', '1', 'true', 't') answer = answer in ('y', 'yes', '1', 'true', 't')
if answer: if not opt.yes and answer:
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
answer = _ConfirmManyUploads() answer = _ConfirmManyUploads()
@ -319,14 +335,15 @@ Gerrit Code Review: https://www.gerritcodereview.com/
if not todo: if not todo:
_die("nothing uncommented for upload") _die("nothing uncommented for upload")
many_commits = False if not opt.yes:
for branch in todo: many_commits = False
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: for branch in todo:
many_commits = True if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
break many_commits = True
if many_commits: break
if not _ConfirmManyUploads(multiple_branches=True): if many_commits:
_die("upload aborted by user") if not _ConfirmManyUploads(multiple_branches=True):
_die("upload aborted by user")
self._UploadAndReport(opt, todo, people) self._UploadAndReport(opt, todo, people)
@ -370,6 +387,10 @@ Gerrit Code Review: https://www.gerritcodereview.com/
# Check if there are local changes that may have been forgotten # Check if there are local changes that may have been forgotten
changes = branch.project.UncommitedFiles() 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: if changes:
key = 'review.%s.autoupload' % branch.project.remote.review key = 'review.%s.autoupload' % branch.project.remote.review
answer = branch.project.config.GetBoolean(key) answer = branch.project.config.GetBoolean(key)
@ -421,12 +442,6 @@ Gerrit Code Review: https://www.gerritcodereview.com/
labels = set(_ExpandCommaList(branch.project.config.GetString(key))) labels = set(_ExpandCommaList(branch.project.config.GetString(key)))
for label in opt.labels: for label in opt.labels:
labels.update(_ExpandCommaList(label)) labels.update(_ExpandCommaList(label))
# Basic sanity check on label syntax.
for label in labels:
if not re.match(r'^.+[+-][0-9]+$', label):
print('repo: error: invalid label syntax "%s": labels use forms '
'like CodeReview+1 or Verified-1' % (label,), file=sys.stderr)
sys.exit(1)
# Handle e-mail notifications. # Handle e-mail notifications.
if opt.notify is False: if opt.notify is False:
@ -461,6 +476,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
private=opt.private, private=opt.private,
notify=notify, notify=notify,
wip=opt.wip, wip=opt.wip,
ready=opt.ready,
dest_branch=destination, dest_branch=destination,
validate_certs=opt.validate_certs, validate_certs=opt.validate_certs,
push_options=opt.push_options) push_options=opt.push_options)

View File

@ -33,7 +33,7 @@ class Version(Command, MirrorSafeCommand):
def Execute(self, opt, args): def Execute(self, opt, args):
rp = self.manifest.repoProject rp = self.manifest.repoProject
rem = rp.GetRemote(rp.remote.name) rem = rp.GetRemote()
branch = rp.GetBranch('default') branch = rp.GetBranch('default')
# These might not be the same. Report them both. # These might not be the same. Report them both.

View File

@ -24,7 +24,6 @@ from unittest import mock
import git_superproject import git_superproject
import git_trace2_event_log import git_trace2_event_log
import manifest_xml import manifest_xml
import platform_utils
from test_manifest_xml import sort_attributes from test_manifest_xml import sort_attributes
@ -38,7 +37,8 @@ class SuperprojectTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
"""Set up superproject every time.""" """Set up superproject every time."""
self.tempdir = tempfile.mkdtemp(prefix='repo_tests') self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
self.tempdir = self.tempdirobj.name
self.repodir = os.path.join(self.tempdir, '.repo') self.repodir = os.path.join(self.tempdir, '.repo')
self.manifest_file = os.path.join( self.manifest_file = os.path.join(
self.repodir, manifest_xml.MANIFEST_FILE_NAME) self.repodir, manifest_xml.MANIFEST_FILE_NAME)
@ -75,7 +75,7 @@ class SuperprojectTestCase(unittest.TestCase):
def tearDown(self): def tearDown(self):
"""Tear down superproject every time.""" """Tear down superproject every time."""
platform_utils.rmtree(self.tempdir) self.tempdirobj.cleanup()
def getXmlManifest(self, data): def getXmlManifest(self, data):
"""Helper to initialize a manifest for testing.""" """Helper to initialize a manifest for testing."""
@ -312,9 +312,6 @@ class SuperprojectTestCase(unittest.TestCase):
'<project groups="notdefault,platform-' + self.platform + '" ' '<project groups="notdefault,platform-' + self.platform + '" '
'name="platform/art" path="art" ' 'name="platform/art" path="art" '
'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>' 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
'<project clone-depth="1" groups="' + local_group + '" '
'name="platform/vendor/x" path="vendor/x" remote="goog" '
'revision="master-with-vendor"/>'
'<superproject name="superproject"/>' '<superproject name="superproject"/>'
'</manifest>') '</manifest>')

View File

@ -17,7 +17,6 @@
import os import os
import platform import platform
import re import re
import shutil
import tempfile import tempfile
import unittest import unittest
import xml.dom.minidom import xml.dom.minidom
@ -92,7 +91,8 @@ class ManifestParseTestCase(unittest.TestCase):
"""TestCase for parsing manifests.""" """TestCase for parsing manifests."""
def setUp(self): def setUp(self):
self.tempdir = tempfile.mkdtemp(prefix='repo_tests') self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
self.tempdir = self.tempdirobj.name
self.repodir = os.path.join(self.tempdir, '.repo') self.repodir = os.path.join(self.tempdir, '.repo')
self.manifest_dir = os.path.join(self.repodir, 'manifests') self.manifest_dir = os.path.join(self.repodir, 'manifests')
self.manifest_file = os.path.join( self.manifest_file = os.path.join(
@ -111,7 +111,7 @@ class ManifestParseTestCase(unittest.TestCase):
""") """)
def tearDown(self): def tearDown(self):
shutil.rmtree(self.tempdir, ignore_errors=True) self.tempdirobj.cleanup()
def getXmlManifest(self, data): def getXmlManifest(self, data):
"""Helper to initialize a manifest for testing.""" """Helper to initialize a manifest for testing."""
@ -252,6 +252,37 @@ class XmlManifestTests(ManifestParseTestCase):
'<manifest></manifest>') '<manifest></manifest>')
self.assertEqual(manifest.ToDict(), {}) 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): def test_repo_hooks(self):
"""Check repo-hooks settings.""" """Check repo-hooks settings."""
manifest = self.getXmlManifest(""" manifest = self.getXmlManifest("""

View File

@ -17,7 +17,6 @@
import contextlib import contextlib
import os import os
from pathlib import Path from pathlib import Path
import shutil
import subprocess import subprocess
import tempfile import tempfile
import unittest import unittest
@ -32,11 +31,7 @@ import project
@contextlib.contextmanager @contextlib.contextmanager
def TempGitTree(): def TempGitTree():
"""Create a new empty git checkout for testing.""" """Create a new empty git checkout for testing."""
# TODO(vapier): Convert this to tempfile.TemporaryDirectory once we drop with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
# Python 2 support entirely.
try:
tempdir = tempfile.mkdtemp(prefix='repo-tests')
# Tests need to assume, that main is default branch at init, # Tests need to assume, that main is default branch at init,
# which is not supported in config until 2.28. # which is not supported in config until 2.28.
cmd = ['git', 'init'] cmd = ['git', 'init']
@ -50,8 +45,6 @@ def TempGitTree():
cmd += ['--template', templatedir] cmd += ['--template', templatedir]
subprocess.check_call(cmd, cwd=tempdir) subprocess.check_call(cmd, cwd=tempdir)
yield tempdir yield tempdir
finally:
platform_utils.rmtree(tempdir)
class FakeProject(object): class FakeProject(object):
@ -124,14 +117,15 @@ class CopyLinkTestCase(unittest.TestCase):
""" """
def setUp(self): def setUp(self):
self.tempdir = tempfile.mkdtemp(prefix='repo_tests') self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
self.tempdir = self.tempdirobj.name
self.topdir = os.path.join(self.tempdir, 'checkout') self.topdir = os.path.join(self.tempdir, 'checkout')
self.worktree = os.path.join(self.topdir, 'git-project') self.worktree = os.path.join(self.topdir, 'git-project')
os.makedirs(self.topdir) os.makedirs(self.topdir)
os.makedirs(self.worktree) os.makedirs(self.worktree)
def tearDown(self): def tearDown(self):
shutil.rmtree(self.tempdir, ignore_errors=True) self.tempdirobj.cleanup()
@staticmethod @staticmethod
def touch(path): def touch(path):

View File

@ -14,11 +14,9 @@
"""Unittests for the wrapper.py module.""" """Unittests for the wrapper.py module."""
import contextlib
from io import StringIO from io import StringIO
import os import os
import re import re
import shutil
import sys import sys
import tempfile import tempfile
import unittest import unittest
@ -26,22 +24,9 @@ from unittest import mock
import git_command import git_command
import main import main
import platform_utils
import wrapper import wrapper
@contextlib.contextmanager
def TemporaryDirectory():
"""Create a new empty git checkout for testing."""
# TODO(vapier): Convert this to tempfile.TemporaryDirectory once we drop
# Python 2 support entirely.
try:
tempdir = tempfile.mkdtemp(prefix='repo-tests')
yield tempdir
finally:
platform_utils.rmtree(tempdir)
def fixture(*paths): def fixture(*paths):
"""Return a path relative to tests/fixtures. """Return a path relative to tests/fixtures.
""" """
@ -336,19 +321,19 @@ class NeedSetupGnuPG(RepoWrapperTestCase):
def test_missing_dir(self): def test_missing_dir(self):
"""The ~/.repoconfig tree doesn't exist yet.""" """The ~/.repoconfig tree doesn't exist yet."""
with TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = os.path.join(tempdir, 'foo') self.wrapper.home_dot_repo = os.path.join(tempdir, 'foo')
self.assertTrue(self.wrapper.NeedSetupGnuPG()) self.assertTrue(self.wrapper.NeedSetupGnuPG())
def test_missing_keyring(self): def test_missing_keyring(self):
"""The keyring-version file doesn't exist yet.""" """The keyring-version file doesn't exist yet."""
with TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir self.wrapper.home_dot_repo = tempdir
self.assertTrue(self.wrapper.NeedSetupGnuPG()) self.assertTrue(self.wrapper.NeedSetupGnuPG())
def test_empty_keyring(self): def test_empty_keyring(self):
"""The keyring-version file exists, but is empty.""" """The keyring-version file exists, but is empty."""
with TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir self.wrapper.home_dot_repo = tempdir
with open(os.path.join(tempdir, 'keyring-version'), 'w'): with open(os.path.join(tempdir, 'keyring-version'), 'w'):
pass pass
@ -356,7 +341,7 @@ class NeedSetupGnuPG(RepoWrapperTestCase):
def test_old_keyring(self): def test_old_keyring(self):
"""The keyring-version file exists, but it's old.""" """The keyring-version file exists, but it's old."""
with TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir self.wrapper.home_dot_repo = tempdir
with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp: with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp:
fp.write('1.0\n') fp.write('1.0\n')
@ -364,7 +349,7 @@ class NeedSetupGnuPG(RepoWrapperTestCase):
def test_new_keyring(self): def test_new_keyring(self):
"""The keyring-version file exists, and is up-to-date.""" """The keyring-version file exists, and is up-to-date."""
with TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir self.wrapper.home_dot_repo = tempdir
with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp: with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp:
fp.write('1000.0\n') fp.write('1000.0\n')
@ -376,7 +361,7 @@ class SetupGnuPG(RepoWrapperTestCase):
def test_full(self): def test_full(self):
"""Make sure it works completely.""" """Make sure it works completely."""
with TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir:
self.wrapper.home_dot_repo = tempdir self.wrapper.home_dot_repo = tempdir
self.wrapper.gpg_dir = os.path.join(self.wrapper.home_dot_repo, 'gnupg') self.wrapper.gpg_dir = os.path.join(self.wrapper.home_dot_repo, 'gnupg')
self.assertTrue(self.wrapper.SetupGnuPG(True)) self.assertTrue(self.wrapper.SetupGnuPG(True))
@ -426,7 +411,8 @@ class GitCheckoutTestCase(RepoWrapperTestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
# Create a repo to operate on, but do it once per-class. # Create a repo to operate on, but do it once per-class.
cls.GIT_DIR = tempfile.mkdtemp(prefix='repo-rev-tests') cls.tempdirobj = tempfile.TemporaryDirectory(prefix='repo-rev-tests')
cls.GIT_DIR = cls.tempdirobj.name
run_git = wrapper.Wrapper().run_git run_git = wrapper.Wrapper().run_git
remote = os.path.join(cls.GIT_DIR, 'remote') remote = os.path.join(cls.GIT_DIR, 'remote')
@ -455,10 +441,10 @@ class GitCheckoutTestCase(RepoWrapperTestCase):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
if not cls.GIT_DIR: if not cls.tempdirobj:
return return
shutil.rmtree(cls.GIT_DIR) cls.tempdirobj.cleanup()
class ResolveRepoRev(GitCheckoutTestCase): class ResolveRepoRev(GitCheckoutTestCase):