Compare commits

..

64 Commits

Author SHA1 Message Date
5fb9c6a5b3 v2.29.7: Revert back to v2.29.5
This change reverts stable to v2.29.5, to fix clients that received
v2.29.6, and keep future updates simpler.

Change-Id: I2f5c52c466b7321665c9699ccdbf98f928483fee
2022-11-08 00:54:56 +00:00
859d3d9580 GitcInit: fix gitc-init failure
Aligns argument usage of refactored GitcManifest (8c1e9cbef
"manifest_xml: refactor manifest parsing from client management") to fix
the `repo gitc-init` error: `fatal: manifest_file must be abspath`.

Change-Id: I1728032cce3f39ed1077bbb7ef714410c2c49e1a
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/350374
Tested-by: Woody Lin <woodylin@google.com>
Reviewed-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-11-04 17:30:40 +00:00
fa8d939c8f sync: clear preciousObjects when set in error.
If this is a project that is not using object sharing (there is only one
copy of the remote project) then clear preciousObjects.

To override this for a project, run:

  git config --replace-all repo.preservePreciousObjects true

Change-Id: If3ea061c631c5ecd44ead84f68576012e2c7405c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/350235
Reviewed-by: Jonathan Nieder <jrn@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-11-03 23:01:16 +00:00
a6c52f566a Set tracing to always on and save to .repo/TRACE_FILE.
- add `--trace_to_stderr` option so stderr will include trace outputs and any other errors that get sent to stderr
- while TRACE_FILE will only include trace outputs

piggy-backing on: https://gerrit-review.googlesource.com/c/git-repo/+/349154

Change-Id: I3895a84de4b2784f17fac4325521cd5e72e645e2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/350114
Reviewed-by: LaMont Jones <lamontjones@google.com>
Tested-by: Joanna Wang <jojwang@google.com>
2022-11-03 21:07:07 +00:00
0d130d2da0 tests: Make the tests pass for Python < 3.8
Before Python 3.8, xml.dom.minidom sorted the attributes of an element
when writing it to a file, while later versions output the attributes
in the order they were created. Avoid these differences by sorting the
attributes for each element before comparing the generated manifests
with the expected ones.

This corresponds to commit 5d58c18, but for new tests introduced since
it was integrated.

Change-Id: I5c360656a0968e6e8d57eb068c8e87da7dfa61c1
Signed-off-by: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349917
Reviewed-by: LaMont Jones <lamontjones@google.com>
2022-10-28 17:26:48 +00:00
b750b48f50 init: add --manifest-depth for shallow manifest clone
People rarely care about the history of the manifest repo.  Add a
parameter to specify depth for the manifest.

For now, make the default behavior the same as the current behavior.  At
a future date, the default will be changed to 1.  People who need the
full history should begin passing --manifest-depth=0 to preserve the
behavior when the default changes.

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, https://crbug.com/gerrit/16358
Change-Id: I9130fed3eaed656435c778a85cfe9d04e3a4a6a0
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349814
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-10-27 21:59:09 +00:00
6c8b894d8d Revert "init: change --depth default to 1 for manifest repo"
This reverts commit 076d54652e.

Reason for revert: crbug.com/gerrit/16358

Change-Id: I2970eb50677cca69786f71edffe4aa5271cf139f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349834
Reviewed-by: Sam Saccone <samccone@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Xin Li <delphij@google.com>
2022-10-27 21:52:02 +00:00
b6cfa09500 sync: uninitialized variable on mirror sync failure
When repo sync fails, if the workspace is a mirror, an uninitialized
variable is referenced.

Bug: crbug.com/gerrit/16356
Change-Id: I1dba9f92319b9cbfd18460327560a395c88a089f
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/349654
Reviewed-by: Sam Saccone <samccone@google.com>
Reviewed-by: Xin Li <delphij@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-10-26 23:13:02 +00:00
78dcd3799b sync: do not require python 3.9
Use pre-3.9 syntax for NamedTuple, so that users do not need to have
python 3.9 or later installed.

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

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

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

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

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

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

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

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

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

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

Example:
added projects :
        project_2 at revision e6c8a59832c05dc4b6a68cee6bc0feb832181725

removed projects :
        project_1 at revision e6c8a59832c05dc4b6a68cee6bc0feb832181725

changed projects :
        project_3 changed from 3bb890e1286f04e84d505e5db48e0ada89892331 to e434b3736f11537c67590fefadfe4495895e9785

missing projects :
        project_4

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Also add/update docstrings and comments throughout.

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

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

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

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

Consider this project layout:
<show fs layout>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Change-Id: Idcb038ddeb363368637c58c11346ebf8fd2b27ac
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334939
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: LaMont Jones <lamontjones@google.com>
2022-04-14 00:07:25 +00:00
57 changed files with 1702 additions and 564 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*,
@ -104,6 +105,8 @@ following DTD:
<!ATTLIST extend-project groups CDATA #IMPLIED> <!ATTLIST extend-project groups CDATA #IMPLIED>
<!ATTLIST extend-project revision CDATA #IMPLIED> <!ATTLIST extend-project revision CDATA #IMPLIED>
<!ATTLIST extend-project remote CDATA #IMPLIED> <!ATTLIST extend-project remote CDATA #IMPLIED>
<!ATTLIST extend-project dest-branch CDATA #IMPLIED>
<!ATTLIST extend-project upstream CDATA #IMPLIED>
<!ELEMENT remove-project EMPTY> <!ELEMENT remove-project EMPTY>
<!ATTLIST remove-project name CDATA #REQUIRED> <!ATTLIST remove-project name CDATA #REQUIRED>
@ -302,6 +305,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
@ -419,6 +425,12 @@ project. Same syntax as the corresponding element of `project`.
Attribute `remote`: If specified, overrides the remote of the original Attribute `remote`: If specified, overrides the remote of the original
project. Same syntax as the corresponding element of `project`. project. Same syntax as the corresponding element of `project`.
Attribute `dest-branch`: If specified, overrides the dest-branch of the original
project. Same syntax as the corresponding element of `project`.
Attribute `upstream`: If specified, overrides the upstream of the original
project. Same syntax as the corresponding element of `project`.
### Element annotation ### Element annotation
Zero or more annotation elements may be specified as children of a Zero or more annotation elements may be specified as children of a

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
@ -238,8 +238,8 @@ class Superproject(object):
f'{self._manifest.manifestFile}') f'{self._manifest.manifestFile}')
return SyncResult(False, False) return SyncResult(False, False)
print('NOTICE: --use-superproject is in beta; report any issues to the ' _PrintBetaNotice()
'address described in `repo version`', file=sys.stderr)
should_exit = True should_exit = True
if not self._remote_url: if not self._remote_url:
self._LogWarning(f'superproject URL is not defined in manifest: ' self._LogWarning(f'superproject URL is not defined in manifest: '
@ -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
@ -364,6 +366,13 @@ class Superproject(object):
return UpdateProjectsResult(manifest_path, False) return UpdateProjectsResult(manifest_path, False)
@functools.lru_cache(maxsize=10)
def _PrintBetaNotice():
"""Print the notice of beta status."""
print('NOTICE: --use-superproject is in beta; report any issues to the '
'address described in `repo version`', file=sys.stderr)
@functools.lru_cache(maxsize=None) @functools.lru_cache(maxsize=None)
def _UseSuperprojectFromConfiguration(): def _UseSuperprojectFromConfiguration():
"""Returns the user choice of whether to use superproject.""" """Returns the user choice of whether to use superproject."""
@ -408,16 +417,32 @@ def _UseSuperprojectFromConfiguration():
return False return False
def PrintMessages(opt, manifest): def PrintMessages(use_superproject, manifest):
"""Returns a boolean if error/warning messages are to be printed.""" """Returns a boolean if error/warning messages are to be printed.
return opt.use_superproject is not None or bool(manifest.superproject)
Args:
use_superproject: option value from optparse.
manifest: manifest to use.
"""
return use_superproject is not None or bool(manifest.superproject)
def UseSuperproject(opt, manifest): def UseSuperproject(use_superproject, manifest):
"""Returns a boolean if use-superproject option is enabled.""" """Returns a boolean if use-superproject option is enabled.
if opt.use_superproject is not None: Args:
return opt.use_superproject use_superproject: option value from optparse.
manifest: manifest to use.
Returns:
Whether the superproject should be used.
"""
if not manifest.superproject:
# This (sub) manifest does not have a superproject definition.
return False
elif use_superproject is not None:
return use_superproject
else: else:
client_value = manifest.manifestProject.use_superproject client_value = manifest.manifestProject.use_superproject
if client_value is not None: if client_value is not None:

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:
@ -310,7 +309,7 @@ class _Repo(object):
# (sub)manifest, and then any child submanifests. # (sub)manifest, and then any child submanifests.
result = cmd.Execute(copts, cargs) result = cmd.Execute(copts, cargs)
for submanifest in repo_client.manifest.submanifests.values(): for submanifest in repo_client.manifest.submanifests.values():
spec = submanifest.ToSubmanifestSpec(root=repo_client.outer_client) spec = submanifest.ToSubmanifestSpec()
gopts.submanifest_path = submanifest.repo_client.path_prefix gopts.submanifest_path = submanifest.repo_client.path_prefix
child_argv = argv[:] child_argv = argv[:]
child_argv.append('--no-outer-manifest') child_argv.append('--no-outer-manifest')

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" "October 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 (0 for full clone); see git clone (default: 0)
.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" "October 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 (0 for full clone); see git clone (default: 0)
.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" "October 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*,
@ -165,6 +190,8 @@ CDATA #IMPLIED>
<!ATTLIST extend\-project groups CDATA #IMPLIED> <!ATTLIST extend\-project groups CDATA #IMPLIED>
<!ATTLIST extend\-project revision CDATA #IMPLIED> <!ATTLIST extend\-project revision CDATA #IMPLIED>
<!ATTLIST extend\-project remote CDATA #IMPLIED> <!ATTLIST extend\-project remote CDATA #IMPLIED>
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
.IP .IP
<!ELEMENT remove\-project EMPTY> <!ELEMENT remove\-project EMPTY>
<!ATTLIST remove\-project name CDATA #REQUIRED> <!ATTLIST remove\-project name CDATA #REQUIRED>
@ -295,6 +322,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
@ -401,6 +487,12 @@ project. Same syntax as the corresponding element of `project`.
Attribute `remote`: If specified, overrides the remote of the original project. Attribute `remote`: If specified, overrides the remote of the original project.
Same syntax as the corresponding element of `project`. Same syntax as the corresponding element of `project`.
.PP .PP
Attribute `dest\-branch`: If specified, overrides the dest\-branch of the original
project. Same syntax as the corresponding element of `project`.
.PP
Attribute `upstream`: If specified, overrides the upstream of the original
project. Same syntax as the corresponding element of `project`.
.PP
Element annotation Element annotation
.PP .PP
Zero or more annotation elements may be specified as children of a project or Zero or more annotation elements may be specified as children of a project or
@ -513,10 +605,10 @@ 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
Additional remotes and projects may be added through local manifest files stored Additional remotes and projects may be added through local manifest files stored
in `$TOP_DIR/.repo/local_manifests/*.xml`. in `$TOP_DIR/.repo/local_manifests/*.xml`.

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,9 +214,11 @@ 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.
annotations: (derived) a list of annotations. annotations: (derived) a list of annotations.
present: (derived) a boolean, whether the submanifest's manifest file is present. present: (derived) a boolean, whether the sub manifest file is present.
""" """
def __init__(self, def __init__(self,
name, name,
@ -225,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
@ -233,7 +236,9 @@ 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.annotations = [] self.annotations = []
outer_client = parent._outer_client or parent outer_client = parent._outer_client or parent
if self.remote and not self.project: if self.remote and not self.project:
@ -248,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)
@ -262,26 +268,28 @@ 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))
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(other) return not self.__eq__(other)
def ToSubmanifestSpec(self, root): def ToSubmanifestSpec(self):
"""Return a SubmanifestSpec object, populating attributes""" """Return a SubmanifestSpec object, populating attributes"""
mp = root.manifestProject mp = self.parent.manifestProject
remote = root.remotes[self.remote or root.default.remote.name] remote = self.parent.remotes[self.remote or self.parent.default.remote.name]
# If a project was given, generate the url from the remote and project. # If a project was given, generate the url from the remote and project.
# If not, use this manifestProject's url. # If not, use this manifestProject's url.
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)
@ -298,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))
@ -325,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:
@ -335,22 +348,32 @@ 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
if not outer_client or outer_client == self:
# manifestFileOverrides only exists in the outer_client's manifest, since
# that is the only instance left when Unload() is called on the outer
# manifest.
self.manifestFileOverrides = {}
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`')
@ -396,14 +419,10 @@ class XmlManifest(object):
if not os.path.isfile(path): if not os.path.isfile(path):
raise ManifestParseError('manifest %s not found' % name) raise ManifestParseError('manifest %s not found' % name)
old = self.manifestFile self._load_local_manifests = load_local_manifests
try: self._outer_client.manifestFileOverrides[self.path_prefix] = path
self._load_local_manifests = load_local_manifests self.Unload()
self.manifestFile = path self._Load()
self.Unload()
self._Load()
finally:
self.manifestFile = old
def Link(self, name): def Link(self, name):
"""Update the repo metadata to use a different manifest. """Update the repo metadata to use a different manifest.
@ -469,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':
@ -484,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
@ -529,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:
@ -565,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:
@ -750,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
@ -775,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:
@ -792,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
@ -808,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()
@ -822,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
@ -880,6 +920,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
exclude = self.manifest.manifestProject.partial_clone_exclude or '' exclude = self.manifest.manifestProject.partial_clone_exclude or ''
return set(x.strip() for x in exclude.split(',')) return set(x.strip() for x in exclude.split(','))
def SetManifestOverride(self, path):
"""Override manifestFile. The caller must call Unload()"""
self._outer_client.manifest.manifestFileOverrides[self.path_prefix] = path
@property @property
def UseLocalManifests(self): def UseLocalManifests(self):
return self._load_local_manifests return self._load_local_manifests
@ -960,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.
@ -1005,57 +1054,66 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
# This will load all clients. # This will load all clients.
self._outer_client._Load(initial_client=self) self._outer_client._Load(initial_client=self)
m = self.manifestProject savedManifestFile = self.manifestFile
b = m.GetBranch(m.CurrentBranch).merge override = self._outer_client.manifestFileOverrides.get(self.path_prefix)
if b is not None and b.startswith(R_HEADS): if override:
b = b[len(R_HEADS):] self.manifestFile = override
self.branch = b
parent_groups = self.parent_groups
if self.path_prefix:
parent_groups = f'{SUBMANIFEST_GROUP_PREFIX}:path:{self.path_prefix},{parent_groups}'
# The manifestFile was specified by the user which is why we allow include
# paths to point anywhere.
nodes = []
nodes.append(self._ParseManifestXml(
self.manifestFile, self.manifestProject.worktree,
parent_groups=parent_groups, restrict_includes=False))
if self._load_local_manifests and self.local_manifests:
try:
for local_file in sorted(platform_utils.listdir(self.local_manifests)):
if local_file.endswith('.xml'):
local = os.path.join(self.local_manifests, local_file)
# Since local manifests are entirely managed by the user, allow
# them to point anywhere the user wants.
local_group = f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}'
nodes.append(self._ParseManifestXml(
local, self.subdir,
parent_groups=f'{local_group},{parent_groups}',
restrict_includes=False))
except OSError:
pass
try: try:
self._ParseManifest(nodes) m = self.manifestProject
except ManifestParseError as e: b = m.GetBranch(m.CurrentBranch).merge
# There was a problem parsing, unload ourselves in case they catch if b is not None and b.startswith(R_HEADS):
# this error and try again later, we will show the correct error b = b[len(R_HEADS):]
self.Unload() self.branch = b
raise e
if self.IsMirror: parent_groups = self.parent_groups
self._AddMetaProjectMirror(self.repoProject) if self.path_prefix:
self._AddMetaProjectMirror(self.manifestProject) parent_groups = f'{SUBMANIFEST_GROUP_PREFIX}:path:{self.path_prefix},{parent_groups}'
self._loaded = True # The manifestFile was specified by the user which is why we allow include
# paths to point anywhere.
nodes = []
nodes.append(self._ParseManifestXml(
self.manifestFile, self.manifestProject.worktree,
parent_groups=parent_groups, restrict_includes=False))
# Now that we have loaded this manifest, load any submanifest manifests if self._load_local_manifests and self.local_manifests:
# as well. We need to do this after self._loaded is set to avoid looping. try:
for local_file in sorted(platform_utils.listdir(self.local_manifests)):
if local_file.endswith('.xml'):
local = os.path.join(self.local_manifests, local_file)
# Since local manifests are entirely managed by the user, allow
# them to point anywhere the user wants.
local_group = f'{LOCAL_MANIFEST_GROUP_PREFIX}:{local_file[:-4]}'
nodes.append(self._ParseManifestXml(
local, self.subdir,
parent_groups=f'{local_group},{parent_groups}',
restrict_includes=False))
except OSError:
pass
try:
self._ParseManifest(nodes)
except ManifestParseError as e:
# There was a problem parsing, unload ourselves in case they catch
# this error and try again later, we will show the correct error
self.Unload()
raise e
if self.IsMirror:
self._AddMetaProjectMirror(self.repoProject)
self._AddMetaProjectMirror(self.manifestProject)
self._loaded = True
finally:
if override:
self.manifestFile = savedManifestFile
# Now that we have loaded this manifest, load any submanifests as well.
# 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(self) spec = tree.ToSubmanifestSpec()
present = os.path.exists(os.path.join(self.subdir, MANIFEST_FILE_NAME)) present = os.path.exists(os.path.join(self.subdir, MANIFEST_FILE_NAME))
if present and tree.present and not tree.repo_client: if present and tree.present and not tree.repo_client:
if initial_client and initial_client.topdir == self.topdir: if initial_client and initial_client.topdir == self.topdir:
@ -1231,6 +1289,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
remote = self._default.remote remote = self._default.remote
else: else:
remote = self._get_remote(node) remote = self._get_remote(node)
dest_branch = node.getAttribute('dest-branch')
upstream = node.getAttribute('upstream')
named_projects = self._projects[name] named_projects = self._projects[name]
if dest_path and not path and len(named_projects) > 1: if dest_path and not path and len(named_projects) > 1:
@ -1246,6 +1306,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if remote_name: if remote_name:
p.remote = remote.ToRemoteSpec(name) p.remote = remote.ToRemoteSpec(name)
if dest_branch:
p.dest_branch = dest_branch
if upstream:
p.upstream = upstream
if dest_path: if dest_path:
del self._paths[p.relpath] del self._paths[p.relpath]
@ -1330,7 +1394,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)
@ -1407,8 +1471,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))
@ -1475,6 +1539,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
@ -1495,7 +1560,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':
@ -1619,6 +1684,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.
@ -1626,7 +1695,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
@ -1642,7 +1711,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)
@ -1656,6 +1725,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(
@ -1874,11 +1946,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
fromKeys = sorted(fromProjects.keys()) fromKeys = sorted(fromProjects.keys())
toKeys = sorted(toProjects.keys()) toKeys = sorted(toProjects.keys())
diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []} diff = {'added': [], 'removed': [], 'missing': [], 'changed': [], 'unreachable': []}
for proj in fromKeys: for proj in fromKeys:
if proj not in toKeys: if proj not in toKeys:
diff['removed'].append(fromProjects[proj]) diff['removed'].append(fromProjects[proj])
elif not fromProjects[proj].Exists:
diff['missing'].append(toProjects[proj])
toKeys.remove(proj)
else: else:
fromProj = fromProjects[proj] fromProj = fromProjects[proj]
toProj = toProjects[proj] toProj = toProjects[proj]
@ -1916,6 +1991,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

@ -24,6 +24,11 @@ _NOT_TTY = not os.isatty(2)
# column 0. # column 0.
CSI_ERASE_LINE = '\x1b[2K' CSI_ERASE_LINE = '\x1b[2K'
# This will erase all content in the current line after the cursor. This is
# useful for partial updates & progress messages as the terminal can display
# it better.
CSI_ERASE_LINE_AFTER = '\x1b[K'
def duration_str(total): def duration_str(total):
"""A less noisy timedelta.__str__. """A less noisy timedelta.__str__.
@ -85,10 +90,10 @@ class Progress(object):
return return
if self._total <= 0: if self._total <= 0:
sys.stderr.write('%s\r%s: %d,' % ( sys.stderr.write('\r%s: %d,%s' % (
CSI_ERASE_LINE,
self._title, self._title,
self._done)) self._done,
CSI_ERASE_LINE_AFTER))
sys.stderr.flush() sys.stderr.flush()
else: else:
p = (100 * self._done) / self._total p = (100 * self._done) / self._total
@ -96,14 +101,14 @@ class Progress(object):
jobs = '[%d job%s] ' % (self._active, 's' if self._active > 1 else '') jobs = '[%d job%s] ' % (self._active, 's' if self._active > 1 else '')
else: else:
jobs = '' jobs = ''
sys.stderr.write('%s\r%s: %2d%% %s(%d%s/%d%s)%s%s%s' % ( sys.stderr.write('\r%s: %2d%% %s(%d%s/%d%s)%s%s%s%s' % (
CSI_ERASE_LINE,
self._title, self._title,
p, p,
jobs, jobs,
self._done, self._units, self._done, self._units,
self._total, self._units, self._total, self._units,
' ' if msg else '', msg, ' ' if msg else '', msg,
CSI_ERASE_LINE_AFTER,
'\n' if self._print_newline else '')) '\n' if self._print_newline else ''))
sys.stderr.flush() sys.stderr.flush()
@ -113,19 +118,19 @@ class Progress(object):
duration = duration_str(time() - self._start) duration = duration_str(time() - self._start)
if self._total <= 0: if self._total <= 0:
sys.stderr.write('%s\r%s: %d, done in %s\n' % ( sys.stderr.write('\r%s: %d, done in %s%s\n' % (
CSI_ERASE_LINE,
self._title, self._title,
self._done, self._done,
duration)) duration,
CSI_ERASE_LINE_AFTER))
sys.stderr.flush() sys.stderr.flush()
else: else:
p = (100 * self._done) / self._total p = (100 * self._done) / self._total
sys.stderr.write('%s\r%s: %3d%% (%d%s/%d%s), done in %s\n' % ( sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done in %s%s\n' % (
CSI_ERASE_LINE,
self._title, self._title,
p, p,
self._done, self._units, self._done, self._units,
self._total, self._units, self._total, self._units,
duration)) duration,
CSI_ERASE_LINE_AFTER))
sys.stderr.flush() sys.stderr.flush()

View File

@ -26,12 +26,15 @@ import sys
import tarfile import tarfile
import tempfile import tempfile
import time import time
from typing import NamedTuple
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
@ -43,11 +46,22 @@ from repo_trace import IsTrace, Trace
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
class SyncNetworkHalfResult(NamedTuple):
"""Sync_NetworkHalf return value."""
# True if successful.
success: bool
# Did we query the remote? False when optimized_fetch is True and we have the
# commit already present.
remote_fetched: bool
# Maximum sleep time allowed during retries. # Maximum sleep time allowed during retries.
MAXIMUM_RETRY_SLEEP_SEC = 3600.0 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 +214,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 +227,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 +475,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 +611,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 +661,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 +695,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 +748,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 +794,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 +842,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 +903,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 +915,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 +926,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 +1018,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 +1035,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 +1077,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 +1088,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)
@ -1091,7 +1142,7 @@ class Project(object):
if archive and not isinstance(self, MetaProject): if archive and not isinstance(self, MetaProject):
if self.remote.url.startswith(('http://', 'https://')): if self.remote.url.startswith(('http://', 'https://')):
_error("%s: Cannot fetch archives from http/https remotes.", self.name) _error("%s: Cannot fetch archives from http/https remotes.", self.name)
return False return SyncNetworkHalfResult(False, False)
name = self.relpath.replace('\\', '/') name = self.relpath.replace('\\', '/')
name = name.replace('/', '_') name = name.replace('/', '_')
@ -1102,19 +1153,19 @@ class Project(object):
self._FetchArchive(tarpath, cwd=topdir) self._FetchArchive(tarpath, cwd=topdir)
except GitError as e: except GitError as e:
_error('%s', e) _error('%s', e)
return False return SyncNetworkHalfResult(False, False)
# From now on, we only need absolute tarpath # From now on, we only need absolute tarpath
tarpath = os.path.join(topdir, tarpath) tarpath = os.path.join(topdir, tarpath)
if not self._ExtractArchive(tarpath, path=topdir): if not self._ExtractArchive(tarpath, path=topdir):
return False return SyncNetworkHalfResult(False, True)
try: try:
platform_utils.remove(tarpath) platform_utils.remove(tarpath)
except OSError as e: except OSError as e:
_warn("Cannot remove archive %s: %s", tarpath, str(e)) _warn("Cannot remove archive %s: %s", tarpath, str(e))
self._CopyAndLinkFiles() self._CopyAndLinkFiles()
return True return SyncNetworkHalfResult(True, True)
# If the shared object dir already exists, don't try to rebootstrap with a # If the shared object dir already exists, don't try to rebootstrap with a
# clone bundle download. We should have the majority of objects already. # clone bundle download. We should have the majority of objects already.
@ -1133,6 +1184,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:
@ -1167,9 +1229,11 @@ class Project(object):
depth = self.manifest.manifestProject.depth depth = self.manifest.manifestProject.depth
# See if we can skip the network fetch entirely. # See if we can skip the network fetch entirely.
remote_fetched = False
if not (optimized_fetch and if not (optimized_fetch and
(ID_RE.match(self.revisionExpr) and (ID_RE.match(self.revisionExpr) and
self._CheckForImmutableRevision())): self._CheckForImmutableRevision())):
remote_fetched = True
if not self._RemoteFetch( if not self._RemoteFetch(
initial=is_new, initial=is_new,
quiet=quiet, verbose=verbose, output_redir=output_redir, quiet=quiet, verbose=verbose, output_redir=output_redir,
@ -1178,7 +1242,7 @@ class Project(object):
submodules=submodules, force_sync=force_sync, submodules=submodules, force_sync=force_sync,
ssh_proxy=ssh_proxy, ssh_proxy=ssh_proxy,
clone_filter=clone_filter, retry_fetches=retry_fetches): clone_filter=clone_filter, retry_fetches=retry_fetches):
return False return SyncNetworkHalfResult(False, remote_fetched)
mp = self.manifest.manifestProject mp = self.manifest.manifestProject
dissociate = mp.dissociate dissociate = mp.dissociate
@ -1191,7 +1255,7 @@ class Project(object):
if p.stdout and output_redir: if p.stdout and output_redir:
output_redir.write(p.stdout) output_redir.write(p.stdout)
if p.Wait() != 0: if p.Wait() != 0:
return False return SyncNetworkHalfResult(False, remote_fetched)
platform_utils.remove(alternates_file) platform_utils.remove(alternates_file)
if self.worktree: if self.worktree:
@ -1200,7 +1264,7 @@ class Project(object):
self._InitMirrorHead() self._InitMirrorHead()
platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'), platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
missing_ok=True) missing_ok=True)
return True return SyncNetworkHalfResult(True, remote_fetched)
def PostRepoUpgrade(self): def PostRepoUpgrade(self):
self._InitHooks() self._InitHooks()
@ -1233,7 +1297,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:
@ -1398,6 +1462,8 @@ class Project(object):
cnt_mine += 1 cnt_mine += 1
if not upstream_gain and cnt_mine == len(local_changes): if not upstream_gain and cnt_mine == len(local_changes):
# The copy/linkfile config may have changed.
self._CopyAndLinkFiles()
return return
if self.IsDirty(consider_untracked=False): if self.IsDirty(consider_untracked=False):
@ -1425,7 +1491,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 +1549,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 +1579,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 +1625,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 +1662,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 +1691,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 +1716,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 +2059,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 +2071,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 +2217,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 +2350,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 +2655,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 +2671,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 +2684,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 +2714,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 +2732,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 +2761,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.
@ -3167,7 +3241,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 +3253,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 +3266,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()
@ -3371,73 +3445,139 @@ class ManifestProject(MetaProject):
@property @property
def reference(self): def reference(self):
"""The --reference for this manifest.""" """The --reference for this manifest."""
self.config.GetString('repo.reference') return self.config.GetString('repo.reference')
@property @property
def dissociate(self): def dissociate(self):
"""Whether to dissociate.""" """Whether to dissociate."""
self.config.GetBoolean('repo.dissociate') return self.config.GetBoolean('repo.dissociate')
@property @property
def archive(self): def archive(self):
"""Whether we use archive.""" """Whether we use archive."""
self.config.GetBoolean('repo.archive') return self.config.GetBoolean('repo.archive')
@property @property
def mirror(self): def mirror(self):
"""Whether we use mirror.""" """Whether we use mirror."""
self.config.GetBoolean('repo.mirror') return self.config.GetBoolean('repo.mirror')
@property @property
def use_worktree(self): def use_worktree(self):
"""Whether we use worktree.""" """Whether we use worktree."""
self.config.GetBoolean('repo.worktree') return self.config.GetBoolean('repo.worktree')
@property @property
def clone_bundle(self): def clone_bundle(self):
"""Whether we use clone_bundle.""" """Whether we use clone_bundle."""
self.config.GetBoolean('repo.clonebundle') return self.config.GetBoolean('repo.clonebundle')
@property @property
def submodules(self): def submodules(self):
"""Whether we use submodules.""" """Whether we use submodules."""
self.config.GetBoolean('repo.submodules') return self.config.GetBoolean('repo.submodules')
@property @property
def git_lfs(self): def git_lfs(self):
"""Whether we use git_lfs.""" """Whether we use git_lfs."""
self.config.GetBoolean('repo.git-lfs') return self.config.GetBoolean('repo.git-lfs')
@property @property
def use_superproject(self): def use_superproject(self):
"""Whether we use superproject.""" """Whether we use superproject."""
self.config.GetBoolean('repo.superproject') return self.config.GetBoolean('repo.superproject')
@property @property
def partial_clone(self): def partial_clone(self):
"""Whether this is a partial clone.""" """Whether this is a partial clone."""
self.config.GetBoolean('repo.partialclone') return self.config.GetBoolean('repo.partialclone')
@property @property
def depth(self): def depth(self):
"""Partial clone depth.""" """Partial clone depth."""
self.config.GetString('repo.depth') return self.config.GetString('repo.depth')
@property @property
def clone_filter(self): def clone_filter(self):
"""The clone filter.""" """The clone filter."""
self.config.GetString('repo.clonefilter') return self.config.GetString('repo.clonefilter')
@property @property
def partial_clone_exclude(self): def partial_clone_exclude(self):
"""Partial clone exclude string""" """Partial clone exclude string"""
self.config.GetBoolean('repo.partialcloneexclude') return self.config.GetBoolean('repo.partialcloneexclude')
@property
def manifest_platform(self):
"""The --platform argument from `repo init`."""
return self.config.GetString('manifest.platform')
@property @property
def _platform_name(self): def _platform_name(self):
"""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,
@ -3479,7 +3619,7 @@ class ManifestProject(MetaProject):
platform: a string, restrict the checkout to projects with the specified platform: a string, restrict the checkout to projects with the specified
platform group. platform group.
git_event_log: an EventLog, for git tracing. git_event_log: an EventLog, for git tracing.
tags: a boolean, whether to fetch tags., tags: a boolean, whether to fetch tags.
manifest_name: a string, the name of the manifest file to use. manifest_name: a string, the name of the manifest file to use.
this_manifest_only: a boolean, whether to only operate on the current sub this_manifest_only: a boolean, whether to only operate on the current sub
manifest. manifest.
@ -3490,7 +3630,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:
@ -3584,7 +3724,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()
@ -3620,6 +3760,7 @@ class ManifestProject(MetaProject):
elif platform != 'none': elif platform != 'none':
print('fatal: invalid platform flag', file=sys.stderr) print('fatal: invalid platform flag', file=sys.stderr)
return False return False
self.config.SetString('manifest.platform', platform)
groups = [x for x in groups if x] groups = [x for x in groups if x]
groupstr = ','.join(groups) groupstr = ','.join(groups)
@ -3703,46 +3844,45 @@ 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).success:
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, # Better delete the manifest git dir if we created it; otherwise next
clone_bundle=clone_bundle, # time (when user fixes problems) we won't go through the "is_new" logic.
current_branch_only=current_branch_only, if is_new:
tags=tags, submodules=submodules, platform_utils.rmtree(self.gitdir)
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
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)
try: try:
self.manifest.Link(manifest_name) self.manifest.Link(manifest_name)
@ -3754,7 +3894,7 @@ class ManifestProject(MetaProject):
if not this_manifest_only: if not this_manifest_only:
for submanifest in self.manifest.submanifests.values(): for submanifest in self.manifest.submanifests.values():
spec = submanifest.ToSubmanifestSpec(root=self.manifest.outer_client) spec = submanifest.ToSubmanifestSpec()
submanifest.repo_client.manifestProject.Sync( submanifest.repo_client.manifestProject.Sync(
manifest_url=spec.manifestUrl, manifest_url=spec.manifestUrl,
manifest_branch=spec.revision, manifest_branch=spec.revision,
@ -3783,10 +3923,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 '

View File

@ -59,18 +59,26 @@ def main(argv):
version = RepoSourceVersion() version = RepoSourceVersion()
cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}', cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}',
'-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}', '-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}',
'-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), TOPDIR.joinpath('repo'), '-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), './repo',
'-h', f'help {cmd}'] for cmd in subcmds.all_commands] '-h', f'help {cmd}'] for cmd in subcmds.all_commands]
cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git', cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git',
'-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}', '-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}',
'-o', MANDIR.joinpath('repo.1.tmp'), TOPDIR.joinpath('repo'), '-o', MANDIR.joinpath('repo.1.tmp'), './repo',
'-h', '--help-all']) '-h', '--help-all'])
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
repo_dir = Path(tempdir) / '.repo' tempdir = Path(tempdir)
repo_dir = tempdir / '.repo'
repo_dir.mkdir() repo_dir.mkdir()
(repo_dir / 'repo').symlink_to(TOPDIR) (repo_dir / 'repo').symlink_to(TOPDIR)
# Create a repo wrapper using the active Python executable. We can't pass
# this directly to help2man as it's too simple, so insert it via shebang.
data = (TOPDIR / 'repo').read_text(encoding='utf-8')
tempbin = tempdir / 'repo'
tempbin.write_text(f'#!{sys.executable}\n' + data, encoding='utf-8')
tempbin.chmod(0o755)
# Run all cmd in parallel, and wait for them to finish. # Run all cmd in parallel, and wait for them to finish.
with multiprocessing.Pool() as pool: with multiprocessing.Pool() as pool:
pool.map(partial(worker, cwd=tempdir, check=True), cmdlist) pool.map(partial(worker, cwd=tempdir, check=True), cmdlist)

22
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,10 @@ 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=0, metavar='DEPTH',
help='create a shallow clone of the manifest repo with '
'given depth (0 for full clone); 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 +329,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 +616,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 +1326,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

@ -118,6 +118,16 @@ synced and their revisions won't be found.
self.printRevision(project.revisionExpr) self.printRevision(project.revisionExpr)
self.out.nl() self.out.nl()
if diff['missing']:
self.out.nl()
self.printText('missing projects : \n')
self.out.nl()
for project in diff['missing']:
self.printProject('\t%s' % (project.relpath))
self.printText(' at revision ')
self.printRevision(project.revisionExpr)
self.out.nl()
if diff['changed']: if diff['changed']:
self.out.nl() self.out.nl()
self.printText('changed projects : \n') self.printText('changed projects : \n')

View File

@ -84,6 +84,11 @@ REPO_PROJECT is set to the unique name of the project.
REPO_PATH is the path relative the the root of the client. REPO_PATH is the path relative the the root of the client.
REPO_OUTERPATH is the path of the sub manifest's root relative to the root of
the client.
REPO_INNERPATH is the path relative to the root of the sub manifest.
REPO_REMOTE is the name of the remote system from the manifest. REPO_REMOTE is the name of the remote system from the manifest.
REPO_LREV is the name of the revision from the manifest, translated REPO_LREV is the name of the revision from the manifest, translated
@ -290,8 +295,9 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
env[name] = val env[name] = val
setenv('REPO_PROJECT', project.name) setenv('REPO_PROJECT', project.name)
setenv('REPO_PATH', project.relpath) setenv('REPO_OUTERPATH', project.manifest.path_prefix)
setenv('REPO_OUTERPATH', project.RelPath(local=opt.this_manifest_only)) setenv('REPO_INNERPATH', project.relpath)
setenv('REPO_PATH', project.RelPath(local=opt.this_manifest_only))
setenv('REPO_REMOTE', project.remote.name) setenv('REPO_REMOTE', project.remote.name)
try: try:
# If we aren't in a fully synced state and we don't have the ref the manifest # If we aren't in a fully synced state and we don't have the ref the manifest

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):
@ -260,6 +261,9 @@ to update the working directory files.
if opt.use_superproject is not None: if opt.use_superproject is not None:
self.OptionParser.error('--mirror and --use-superproject cannot be ' self.OptionParser.error('--mirror and --use-superproject cannot be '
'used together.') 'used together.')
if opt.archive and opt.use_superproject is not None:
self.OptionParser.error('--archive and --use-superproject cannot be used '
'together.')
if opt.standalone_manifest and (opt.manifest_branch or if opt.standalone_manifest and (opt.manifest_branch or
opt.manifest_name != 'default.xml'): opt.manifest_name != 'default.xml'):
@ -311,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

@ -51,7 +51,7 @@ need to be performed by an end-user.
_PostRepoUpgrade(self.manifest) _PostRepoUpgrade(self.manifest)
else: else:
if not rp.Sync_NetworkHalf(): if not rp.Sync_NetworkHalf().success:
print("error: can't update repo", file=sys.stderr) print("error: can't update repo", file=sys.stderr)
sys.exit(1) sys.exit(1)

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)

File diff suppressed because it is too large Load Diff

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("""
@ -843,3 +874,27 @@ class ExtendProjectElementTests(ManifestParseTestCase):
else: else:
self.assertEqual(manifest.projects[0].relpath, 'bar') self.assertEqual(manifest.projects[0].relpath, 'bar')
self.assertEqual(manifest.projects[1].relpath, 'y') self.assertEqual(manifest.projects[1].relpath, 'y')
def test_extend_project_dest_branch(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" dest-branch="foo" />
<project name="myproject" />
<extend-project name="myproject" dest-branch="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].dest_branch, 'bar')
def test_extend_project_upstream(self):
manifest = self.getXmlManifest("""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="myproject" />
<extend-project name="myproject" upstream="bar" />
</manifest>
""")
self.assertEqual(len(manifest.projects), 1)
self.assertEqual(manifest.projects[0].upstream, 'bar')

View File

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

@ -42,4 +42,4 @@ def test_get_current_branch_only(use_superproject, cli_args, result):
opts, _ = cmd.OptionParser.parse_args(cli_args) opts, _ = cmd.OptionParser.parse_args(cli_args)
with mock.patch('git_superproject.UseSuperproject', return_value=use_superproject): with mock.patch('git_superproject.UseSuperproject', return_value=use_superproject):
assert cmd._GetCurrentBranchOnly(opts) == result assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result

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):