mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-07-04 20:17:16 +00:00
Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
98bb76577d | |||
d33dce0b77 | |||
89ed8acdbe | |||
71e48b7672 | |||
13576a8caf | |||
2345906d04 | |||
41289c62b4 | |||
c72bd8486a | |||
d53cb9549a | |||
cf0ba48649 | |||
2a089cfee4 | |||
4a478edb44 | |||
6bd89aa657 | |||
9c1fc5bc5d | |||
333c0a499b | |||
fdeb20f43f | |||
bf40957b38 | |||
f9e81c922d | |||
e6601067ed | |||
3001d6a426 | |||
00c5ea3787 | |||
0531a623e1 | |||
2273f46cb3 | |||
7b9b251a5e | |||
6251729cb4 | |||
11b30b91df | |||
198838599c | |||
282d0cae89 | |||
03ff276cd7 | |||
4ee4a45d03 | |||
0f6f16ed17 | |||
76491590b8 | |||
6a74c91f50 | |||
669efd0fd7 | |||
a0f6006ae7 | |||
2ddbf8a8bf | |||
445723fd37 | |||
436bde5137 | |||
4f88206178 | |||
f88282ccc2 | |||
8967a5aec6 | |||
2f3c3316e4 | |||
37c21c268b | |||
b12c369e0b | |||
bbe8836494 | |||
9d96f58f5f | |||
7a1e7e772f | |||
c474c9cba1 | |||
956f7363d1 | |||
6f8c1bf4ff | |||
e0b16a22a0 | |||
d669d2dee5 | |||
366824937c | |||
a84f43a006 | |||
0468feac39 | |||
0ec2029833 | |||
d8e8ae8990 | |||
6448a4f2af | |||
1328c35a4d | |||
148e1ce81a | |||
32ca6687ae | |||
0ae9503a86 | |||
d92076d930 | |||
aeb2eee9d3 | |||
45d1c372a7 | |||
19607b2817 | |||
68744dbc01 | |||
ef412624e9 | |||
a06ab7d28b | |||
471a7ed5f7 | |||
619a2b5887 | |||
ab15e42fa4 | |||
75c02fe4cb | |||
afd1b4023f |
5
.gitreview
Normal file
5
.gitreview
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[gerrit]
|
||||||
|
host=gerrit-review.googlesource.com
|
||||||
|
scheme=https
|
||||||
|
project=git-repo.git
|
||||||
|
defaultbranch=main
|
@ -3,7 +3,7 @@
|
|||||||
# Short Version
|
# Short Version
|
||||||
|
|
||||||
- Make small logical changes.
|
- Make small logical changes.
|
||||||
- Provide a meaningful commit message.
|
- [Provide a meaningful commit message][commit-message-style].
|
||||||
- Check for coding errors and style nits with flake8.
|
- Check for coding errors and style nits with flake8.
|
||||||
- Make sure all code is under the Apache License, 2.0.
|
- Make sure all code is under the Apache License, 2.0.
|
||||||
- Publish your changes for review.
|
- Publish your changes for review.
|
||||||
@ -26,10 +26,11 @@ yourself with the following relevant bits.
|
|||||||
|
|
||||||
## Make separate commits for logically separate changes.
|
## Make separate commits for logically separate changes.
|
||||||
|
|
||||||
Unless your patch is really trivial, you should not be sending
|
Unless your patch is really trivial, you should not be sending out a patch that
|
||||||
out a patch that was generated between your working tree and your
|
was generated between your working tree and your commit head.
|
||||||
commit head. Instead, always make a commit with complete commit
|
Instead, always make a commit with a complete
|
||||||
message and generate a series of patches from your repository.
|
[commit message][commit-message-style] and generate a series of patches from
|
||||||
|
your repository.
|
||||||
It is a good discipline.
|
It is a good discipline.
|
||||||
|
|
||||||
Describe the technical detail of the change(s).
|
Describe the technical detail of the change(s).
|
||||||
@ -171,3 +172,6 @@ After you receive a Code-Review+2 from the maintainer, select the Verified
|
|||||||
button on the gerrit page for the change. This verifies that you have tested
|
button on the gerrit page for the change. This verifies that you have tested
|
||||||
your changes and notifies the maintainer that they are ready to be submitted.
|
your changes and notifies the maintainer that they are ready to be submitted.
|
||||||
The maintainer will then submit your changes to the repository.
|
The maintainer will then submit your changes to the repository.
|
||||||
|
|
||||||
|
|
||||||
|
[commit-message-style]: https://chris.beams.io/posts/git-commit/
|
||||||
|
@ -157,6 +157,7 @@ User controlled settings are initialized when running `repo init`.
|
|||||||
| Setting | `repo init` Option | Use/Meaning |
|
| Setting | `repo init` Option | Use/Meaning |
|
||||||
|------------------- |---------------------------|-------------|
|
|------------------- |---------------------------|-------------|
|
||||||
| manifest.groups | `--groups` & `--platform` | The manifest groups to sync |
|
| manifest.groups | `--groups` & `--platform` | The manifest groups to sync |
|
||||||
|
| manifest.standalone | `--standalone-manifest` | Download manifest as static file instead of creating checkout |
|
||||||
| repo.archive | `--archive` | Use `git archive` for checkouts |
|
| repo.archive | `--archive` | Use `git archive` for checkouts |
|
||||||
| repo.clonebundle | `--clone-bundle` | Whether the initial sync used clone.bundle explicitly |
|
| repo.clonebundle | `--clone-bundle` | Whether the initial sync used clone.bundle explicitly |
|
||||||
| repo.clonefilter | `--clone-filter` | Filter setting when using [partial git clones] |
|
| repo.clonefilter | `--clone-filter` | Filter setting when using [partial git clones] |
|
||||||
|
@ -90,6 +90,7 @@ following DTD:
|
|||||||
<!ELEMENT extend-project EMPTY>
|
<!ELEMENT extend-project EMPTY>
|
||||||
<!ATTLIST extend-project name CDATA #REQUIRED>
|
<!ATTLIST extend-project name CDATA #REQUIRED>
|
||||||
<!ATTLIST extend-project path CDATA #IMPLIED>
|
<!ATTLIST extend-project path CDATA #IMPLIED>
|
||||||
|
<!ATTLIST extend-project dest-path 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>
|
||||||
@ -103,8 +104,9 @@ following DTD:
|
|||||||
<!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
|
<!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
|
||||||
|
|
||||||
<!ELEMENT superproject EMPTY>
|
<!ELEMENT superproject EMPTY>
|
||||||
<!ATTLIST superproject name CDATA #REQUIRED>
|
<!ATTLIST superproject name CDATA #REQUIRED>
|
||||||
<!ATTLIST superproject remote IDREF #IMPLIED>
|
<!ATTLIST superproject remote IDREF #IMPLIED>
|
||||||
|
<!ATTLIST superproject revision CDATA #IMPLIED>
|
||||||
|
|
||||||
<!ELEMENT contactinfo EMPTY>
|
<!ELEMENT contactinfo EMPTY>
|
||||||
<!ATTLIST contactinfo bugurl CDATA #REQUIRED>
|
<!ATTLIST contactinfo bugurl CDATA #REQUIRED>
|
||||||
@ -336,6 +338,11 @@ against changes to the original manifest.
|
|||||||
Attribute `path`: If specified, limit the change to projects checked out
|
Attribute `path`: If specified, limit the change to projects checked out
|
||||||
at the specified path, rather than all projects with the given name.
|
at the specified path, rather than all projects with the given name.
|
||||||
|
|
||||||
|
Attribute `dest-path`: If specified, a path relative to the top directory
|
||||||
|
of the repo client where the Git working directory for this project
|
||||||
|
should be placed. This is used to move a project in the checkout by
|
||||||
|
overriding the existing `path` setting.
|
||||||
|
|
||||||
Attribute `groups`: List of additional groups to which this project
|
Attribute `groups`: List of additional groups to which this project
|
||||||
belongs. Same syntax as the corresponding element of `project`.
|
belongs. Same syntax as the corresponding element of `project`.
|
||||||
|
|
||||||
@ -432,6 +439,11 @@ same meaning as project's name attribute. See the
|
|||||||
Attribute `remote`: Name of a previously defined remote element.
|
Attribute `remote`: Name of a previously defined remote element.
|
||||||
If not supplied the remote given by the default element is used.
|
If not supplied the remote given by the default element is used.
|
||||||
|
|
||||||
|
Attribute `revision`: Name of the Git branch the manifest wants
|
||||||
|
to track for this superproject. If not supplied the revision given
|
||||||
|
by the remote element is used if applicable, else the default
|
||||||
|
element is used.
|
||||||
|
|
||||||
### Element contactinfo
|
### Element contactinfo
|
||||||
|
|
||||||
***
|
***
|
||||||
|
@ -208,85 +208,132 @@ Things in bold indicate stuff to take note of, but does not guarantee that we
|
|||||||
still support them.
|
still support them.
|
||||||
Things in italics are things we used to care about but probably don't anymore.
|
Things in italics are things we used to care about but probably don't anymore.
|
||||||
|
|
||||||
| Date | EOL | [Git][rel-g] | [Python][rel-p] | [Ubuntu][rel-u] / [Debian][rel-d] | Git | Python |
|
| Date | EOL | [Git][rel-g] | [Python][rel-p] | [SSH][rel-o] | [Ubuntu][rel-u] / [Debian][rel-d] | Git | Python | SSH |
|
||||||
|:--------:|:------------:|--------------|-----------------|-----------------------------------|-----|--------|
|
|:--------:|:------------:|:------------:|:---------------:|:------------:|-----------------------------------|-----|--------|-----|
|
||||||
| Oct 2008 | *Oct 2013* | | 2.6.0 | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
|
| Apr 2008 | | | | 5.0 |
|
||||||
|
| Jun 2008 | | | | 5.1 |
|
||||||
|
| Oct 2008 | *Oct 2013* | | 2.6.0 | | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
|
||||||
| Dec 2008 | *Feb 2009* | | 3.0.0 |
|
| Dec 2008 | *Feb 2009* | | 3.0.0 |
|
||||||
| Feb 2009 | *Mar 2012* | | | Debian 5 Lenny | 1.5.6.5 | 2.5.2 |
|
| Feb 2009 | | | | 5.2 |
|
||||||
| Jun 2009 | *Jun 2016* | | 3.1.0 | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
|
| Feb 2009 | *Mar 2012* | | | | Debian 5 Lenny | 1.5.6.5 | 2.5.2 |
|
||||||
| Feb 2010 | *Oct 2012* | 1.7.0 | | *10.04 Lucid* - *12.04 Precise* - 12.10 Quantal |
|
| Jun 2009 | *Jun 2016* | | 3.1.0 | | *10.04 Lucid* - 10.10 Maverick / *Squeeze* |
|
||||||
| Apr 2010 | *Apr 2015* | | | *10.04 Lucid* | 1.7.0.4 | 2.6.5 3.1.2 |
|
| Sep 2009 | | | | 5.3 | *10.04 Lucid* |
|
||||||
| Jul 2010 | *Dec 2019* | | **2.7.0** | 11.04 Natty - **<current>** |
|
| Feb 2010 | *Oct 2012* | 1.7.0 | | | *10.04 Lucid* - *12.04 Precise* - 12.10 Quantal |
|
||||||
| Oct 2010 | | | | 10.10 Maverick | 1.7.1 | 2.6.6 3.1.3 |
|
| Mar 2010 | | | | 5.4 |
|
||||||
| Feb 2011 | *Feb 2016* | | | Debian 6 Squeeze | 1.7.2.5 | 2.6.6 3.1.3 |
|
| Apr 2010 | | | | 5.5 | 10.10 Maverick |
|
||||||
| Apr 2011 | | | | 11.04 Natty | 1.7.4 | 2.7.1 3.2.0 |
|
| Apr 2010 | *Apr 2015* | | | | *10.04 Lucid* | 1.7.0.4 | 2.6.5 3.1.2 | 5.3 |
|
||||||
| Oct 2011 | *Feb 2016* | | 3.2.0 | 11.04 Natty - 12.10 Quantal |
|
| Jul 2010 | *Dec 2019* | | *2.7.0* | | 11.04 Natty - *<current>* |
|
||||||
| Oct 2011 | | | | 11.10 Ocelot | 1.7.5.4 | 2.7.2 3.2.2 |
|
| Aug 2010 | | | | 5.6 |
|
||||||
| Apr 2012 | *Apr 2019* | | | *12.04 Precise* | 1.7.9.5 | 2.7.3 3.2.3 |
|
| Oct 2010 | | | | | 10.10 Maverick | 1.7.1 | 2.6.6 3.1.3 | 5.5 |
|
||||||
| Sep 2012 | *Sep 2017* | | 3.3.0 | 13.04 Raring - 13.10 Saucy |
|
| Jan 2011 | | | | 5.7 |
|
||||||
| Oct 2012 | *Dec 2014* | 1.8.0 | | 13.04 Raring - 13.10 Saucy |
|
| Feb 2011 | | | | 5.8 | 11.04 Natty |
|
||||||
| Oct 2012 | | | | 12.10 Quantal | 1.7.10.4 | 2.7.3 3.2.3 |
|
| Feb 2011 | *Feb 2016* | | | | Debian 6 Squeeze | 1.7.2.5 | 2.6.6 3.1.3 |
|
||||||
| Apr 2013 | | | | 13.04 Raring | 1.8.1.2 | 2.7.4 3.3.1 |
|
| Apr 2011 | | | | | 11.04 Natty | 1.7.4 | 2.7.1 3.2.0 | 5.8 |
|
||||||
| May 2013 | *May 2018* | | | Debian 7 Wheezy | 1.7.10.4 | 2.7.3 3.2.3 |
|
| Sep 2011 | | | | 5.9 | *12.04 Precise* |
|
||||||
| Oct 2013 | | | | 13.10 Saucy | 1.8.3.2 | 2.7.5 3.3.2 |
|
| Oct 2011 | *Feb 2016* | | 3.2.0 | | 11.04 Natty - 12.10 Quantal |
|
||||||
| Feb 2014 | *Dec 2014* | **1.9.0** | | **14.04 Trusty** |
|
| Oct 2011 | | | | | 11.10 Ocelot | 1.7.5.4 | 2.7.2 3.2.2 | 5.8 |
|
||||||
| Mar 2014 | *Mar 2019* | | **3.4.0** | **14.04 Trusty** - 15.10 Wily / **Jessie** |
|
| Apr 2012 | | | | 6.0 | 12.10 Quantal |
|
||||||
| Apr 2014 | **Apr 2022** | | | **14.04 Trusty** | 1.9.1 | 2.7.5 3.4.0 |
|
| Apr 2012 | *Apr 2019* | | | | *12.04 Precise* | 1.7.9.5 | 2.7.3 3.2.3 | 5.9 |
|
||||||
|
| Aug 2012 | | | | 6.1 | 13.04 Raring |
|
||||||
|
| Sep 2012 | *Sep 2017* | | 3.3.0 | | 13.04 Raring - 13.10 Saucy |
|
||||||
|
| Oct 2012 | *Dec 2014* | 1.8.0 | | | 13.04 Raring - 13.10 Saucy |
|
||||||
|
| Oct 2012 | | | | | 12.10 Quantal | 1.7.10.4 | 2.7.3 3.2.3 | 6.0 |
|
||||||
|
| Mar 2013 | | | | 6.2 | 13.10 Saucy |
|
||||||
|
| Apr 2013 | | | | | 13.04 Raring | 1.8.1.2 | 2.7.4 3.3.1 | 6.1 |
|
||||||
|
| May 2013 | *May 2018* | | | | Debian 7 Wheezy | 1.7.10.4 | 2.7.3 3.2.3 |
|
||||||
|
| Sep 2013 | | | | 6.3 |
|
||||||
|
| Oct 2013 | | | | | 13.10 Saucy | 1.8.3.2 | 2.7.5 3.3.2 | 6.2 |
|
||||||
|
| Nov 2013 | | | | 6.4 |
|
||||||
|
| Jan 2014 | | | | 6.5 |
|
||||||
|
| Feb 2014 | *Dec 2014* | **1.9.0** | | | *14.04 Trusty* |
|
||||||
|
| Mar 2014 | *Mar 2019* | | *3.4.0* | | *14.04 Trusty* - 15.10 Wily / *Jessie* |
|
||||||
|
| Mar 2014 | | | | 6.6 | *14.04 Trusty* - 14.10 Utopic |
|
||||||
|
| Apr 2014 | *Apr 2022* | | | | *14.04 Trusty* | 1.9.1 | 2.7.5 3.4.0 | 6.6 |
|
||||||
| May 2014 | *Dec 2014* | 2.0.0 |
|
| May 2014 | *Dec 2014* | 2.0.0 |
|
||||||
| Aug 2014 | *Dec 2014* | **2.1.0** | | 14.10 Utopic - 15.04 Vivid / **Jessie** |
|
| Aug 2014 | *Dec 2014* | *2.1.0* | | | 14.10 Utopic - 15.04 Vivid / *Jessie* |
|
||||||
| Oct 2014 | | | | 14.10 Utopic | 2.1.0 | 2.7.8 3.4.2 |
|
| Oct 2014 | | | | 6.7 | 15.04 Vivid |
|
||||||
|
| Oct 2014 | | | | | 14.10 Utopic | 2.1.0 | 2.7.8 3.4.2 | 6.6 |
|
||||||
| Nov 2014 | *Sep 2015* | 2.2.0 |
|
| Nov 2014 | *Sep 2015* | 2.2.0 |
|
||||||
| Feb 2015 | *Sep 2015* | 2.3.0 |
|
| Feb 2015 | *Sep 2015* | 2.3.0 |
|
||||||
|
| Mar 2015 | | | | 6.8 |
|
||||||
| Apr 2015 | *May 2017* | 2.4.0 |
|
| Apr 2015 | *May 2017* | 2.4.0 |
|
||||||
| Apr 2015 | **Jun 2020** | | | **Debian 8 Jessie** | 2.1.4 | 2.7.9 3.4.2 |
|
| Apr 2015 | *Jun 2020* | | | | *Debian 8 Jessie* | 2.1.4 | 2.7.9 3.4.2 |
|
||||||
| Apr 2015 | | | | 15.04 Vivid | 2.1.4 | 2.7.9 3.4.3 |
|
| Apr 2015 | | | | | 15.04 Vivid | 2.1.4 | 2.7.9 3.4.3 | 6.7 |
|
||||||
| Jul 2015 | *May 2017* | 2.5.0 | | 15.10 Wily |
|
| Jul 2015 | *May 2017* | 2.5.0 | | | 15.10 Wily |
|
||||||
|
| Jul 2015 | | | | 6.9 | 15.10 Wily |
|
||||||
|
| Aug 2015 | | | | 7.0 |
|
||||||
|
| Aug 2015 | | | | 7.1 |
|
||||||
| Sep 2015 | *May 2017* | 2.6.0 |
|
| Sep 2015 | *May 2017* | 2.6.0 |
|
||||||
| Sep 2015 | **Sep 2020** | | **3.5.0** | **16.04 Xenial** - 17.04 Zesty / **Stretch** |
|
| Sep 2015 | *Sep 2020* | | *3.5.0* | | *16.04 Xenial* - 17.04 Zesty / *Stretch* |
|
||||||
| Oct 2015 | | | | 15.10 Wily | 2.5.0 | 2.7.9 3.4.3 |
|
| Oct 2015 | | | | | 15.10 Wily | 2.5.0 | 2.7.9 3.4.3 | 6.9 |
|
||||||
| Jan 2016 | *Jul 2017* | **2.7.0** | | **16.04 Xenial** |
|
| Jan 2016 | *Jul 2017* | *2.7.0* | | | *16.04 Xenial* |
|
||||||
|
| Feb 2016 | | | | 7.2 | *16.04 Xenial* |
|
||||||
| Mar 2016 | *Jul 2017* | 2.8.0 |
|
| Mar 2016 | *Jul 2017* | 2.8.0 |
|
||||||
| Apr 2016 | **Apr 2024** | | | **16.04 Xenial** | 2.7.4 | 2.7.11 3.5.1 |
|
| Apr 2016 | *Apr 2024* | | | | *16.04 Xenial* | 2.7.4 | 2.7.11 3.5.1 | 7.2 |
|
||||||
| Jun 2016 | *Jul 2017* | 2.9.0 | | 16.10 Yakkety |
|
| Jun 2016 | *Jul 2017* | 2.9.0 | | | 16.10 Yakkety |
|
||||||
|
| Jul 2016 | | | | 7.3 | 16.10 Yakkety |
|
||||||
| Sep 2016 | *Sep 2017* | 2.10.0 |
|
| Sep 2016 | *Sep 2017* | 2.10.0 |
|
||||||
| Oct 2016 | | | | 16.10 Yakkety | 2.9.3 | 2.7.11 3.5.1 |
|
| Oct 2016 | | | | | 16.10 Yakkety | 2.9.3 | 2.7.11 3.5.1 | 7.3 |
|
||||||
| Nov 2016 | *Sep 2017* | **2.11.0** | | 17.04 Zesty / **Stretch** |
|
| Nov 2016 | *Sep 2017* | *2.11.0* | | | 17.04 Zesty / *Stretch* |
|
||||||
| Dec 2016 | **Dec 2021** | | **3.6.0** | 17.10 Artful - **18.04 Bionic** - 18.10 Cosmic |
|
| Dec 2016 | **Dec 2021** | | **3.6.0** | | 17.10 Artful - **18.04 Bionic** - 18.10 Cosmic |
|
||||||
|
| Dec 2016 | | | | 7.4 | 17.04 Zesty / *Debian 9 Stretch* |
|
||||||
| Feb 2017 | *Sep 2017* | 2.12.0 |
|
| Feb 2017 | *Sep 2017* | 2.12.0 |
|
||||||
| Apr 2017 | | | | 17.04 Zesty | 2.11.0 | 2.7.13 3.5.3 |
|
| Mar 2017 | | | | 7.5 | 17.10 Artful |
|
||||||
|
| Apr 2017 | | | | | 17.04 Zesty | 2.11.0 | 2.7.13 3.5.3 | 7.4 |
|
||||||
| May 2017 | *May 2018* | 2.13.0 |
|
| May 2017 | *May 2018* | 2.13.0 |
|
||||||
| Jun 2017 | **Jun 2022** | | | **Debian 9 Stretch** | 2.11.0 | 2.7.13 3.5.3 |
|
| Jun 2017 | *Jun 2022* | | | | *Debian 9 Stretch* | 2.11.0 | 2.7.13 3.5.3 | 7.4 |
|
||||||
| Aug 2017 | *Dec 2019* | 2.14.0 | | 17.10 Artful |
|
| Aug 2017 | *Dec 2019* | 2.14.0 | | | 17.10 Artful |
|
||||||
| Oct 2017 | *Dec 2019* | 2.15.0 |
|
| Oct 2017 | *Dec 2019* | 2.15.0 |
|
||||||
| Oct 2017 | | | | 17.10 Artful | 2.14.1 | 2.7.14 3.6.3 |
|
| Oct 2017 | | | | 7.6 | **18.04 Bionic** |
|
||||||
|
| Oct 2017 | | | | | 17.10 Artful | 2.14.1 | 2.7.14 3.6.3 | 7.5 |
|
||||||
| Jan 2018 | *Dec 2019* | 2.16.0 |
|
| Jan 2018 | *Dec 2019* | 2.16.0 |
|
||||||
| Apr 2018 | *Dec 2019* | 2.17.0 | | **18.04 Bionic** |
|
| Apr 2018 | *Mar 2021* | **2.17.0** | | | **18.04 Bionic** |
|
||||||
| Apr 2018 | **Apr 2028** | | | **18.04 Bionic** | 2.17.0 | 2.7.15 3.6.5 |
|
| Apr 2018 | | | | 7.7 | 18.10 Cosmic |
|
||||||
| Jun 2018 | *Dec 2019* | 2.18.0 |
|
| Apr 2018 | **Apr 2028** | | | | **18.04 Bionic** | 2.17.0 | 2.7.15 3.6.5 | 7.6 |
|
||||||
| Jun 2018 | **Jun 2023** | | 3.7.0 | 19.04 Disco - **20.04 Focal** / **Buster** |
|
| Jun 2018 | *Mar 2021* | 2.18.0 |
|
||||||
| Sep 2018 | *Dec 2019* | 2.19.0 | | 18.10 Cosmic |
|
| Jun 2018 | **Jun 2023** | | 3.7.0 | | 19.04 Disco - **20.04 Focal** / **Buster** |
|
||||||
| Oct 2018 | | | | 18.10 Cosmic | 2.19.1 | 2.7.15 3.6.6 |
|
| Aug 2018 | | | | 7.8 |
|
||||||
| Dec 2018 | *Dec 2019* | **2.20.0** | | 19.04 Disco / **Buster** |
|
| Sep 2018 | *Mar 2021* | 2.19.0 | | | 18.10 Cosmic |
|
||||||
| Feb 2019 | *Dec 2019* | 2.21.0 |
|
| Oct 2018 | | | | 7.9 | 19.04 Disco / **Buster** |
|
||||||
| Apr 2019 | | | | 19.04 Disco | 2.20.1 | 2.7.16 3.7.3 |
|
| Oct 2018 | | | | | 18.10 Cosmic | 2.19.1 | 2.7.15 3.6.6 | 7.7 |
|
||||||
|
| Dec 2018 | *Mar 2021* | **2.20.0** | | | 19.04 Disco - 19.10 Eoan / **Buster** |
|
||||||
|
| Feb 2019 | *Mar 2021* | 2.21.0 |
|
||||||
|
| Apr 2019 | | | | 8.0 | 19.10 Eoan |
|
||||||
|
| Apr 2019 | | | | | 19.04 Disco | 2.20.1 | 2.7.16 3.7.3 | 7.9 |
|
||||||
| Jun 2019 | | 2.22.0 |
|
| Jun 2019 | | 2.22.0 |
|
||||||
| Jul 2019 | **Jul 2024** | | | **Debian 10 Buster** | 2.20.1 | 2.7.16 3.7.3 |
|
| Jul 2019 | **Jul 2024** | | | | **Debian 10 Buster** | 2.20.1 | 2.7.16 3.7.3 | 7.9 |
|
||||||
| Aug 2019 | | 2.23.0 |
|
| Aug 2019 | *Mar 2021* | 2.23.0 |
|
||||||
| Oct 2019 | **Oct 2024** | | 3.8.0 |
|
| Oct 2019 | **Oct 2024** | | 3.8.0 | | **20.04 Focal** - 20.10 Groovy |
|
||||||
| Oct 2019 | | | | 19.10 Eoan | 2.20.1 | 2.7.17 3.7.5 |
|
| Oct 2019 | | | | 8.1 |
|
||||||
| Nov 2019 | | 2.24.0 |
|
| Oct 2019 | | | | | 19.10 Eoan | 2.20.1 | 2.7.17 3.7.5 | 8.0 |
|
||||||
| Jan 2020 | | 2.25.0 | | **20.04 Focal** |
|
| Nov 2019 | *Mar 2021* | 2.24.0 |
|
||||||
| Apr 2020 | **Apr 2030** | | | **20.04 Focal** | 2.25.0 | 2.7.17 3.7.5 |
|
| Jan 2020 | *Mar 2021* | 2.25.0 | | | **20.04 Focal** |
|
||||||
| Oct 2020 | **Oct 2025** | | 3.9.0 | 21.04 Hirsute / **Bullseye** |
|
| Feb 2020 | | | | 8.2 | **20.04 Focal** |
|
||||||
| Dec 2020 | | 2.30.0 | | 21.04 Hirsute / **Bullseye** |
|
| Mar 2020 | *Mar 2021* | 2.26.0 |
|
||||||
| Apr 2021 | *Jan 2022* | | | 21.04 Hirsute | 2.30.2 | 2.7.18 3.9.4 |
|
| Apr 2020 | **Apr 2030** | | | | **20.04 Focal** | 2.25.1 | 2.7.17 3.8.2 | 8.2 |
|
||||||
| Aug 2021 | **Aug 2026** | | | **Debian 11 Bullseye** | 2.30.2 | 2.7.18 3.9.2 |
|
| May 2020 | *Mar 2021* | 2.27.0 | | | 20.10 Groovy |
|
||||||
| **Date** | **EOL** | **[Git][rel-g]** | **[Python][rel-p]** | **[Ubuntu][rel-u] / [Debian][rel-d]** | **Git** | **Python** |
|
| May 2020 | | | | 8.3 |
|
||||||
|
| Jul 2020 | *Mar 2021* | 2.28.0 |
|
||||||
|
| Sep 2020 | | | | 8.4 | 21.04 Hirsute / **Bullseye** |
|
||||||
|
| Oct 2020 | *Mar 2021* | 2.29.0 |
|
||||||
|
| Oct 2020 | | | | | 20.10 Groovy | 2.27.0 | 2.7.18 3.8.6 | 8.3 |
|
||||||
|
| Oct 2020 | **Oct 2025** | | 3.9.0 | | 21.04 Hirsute / **Bullseye** |
|
||||||
|
| Dec 2020 | *Mar 2021* | 2.30.0 | | | 21.04 Hirsute / **Bullseye** |
|
||||||
|
| Mar 2021 | | 2.31.0 |
|
||||||
|
| Mar 2021 | | | | 8.5 |
|
||||||
|
| Apr 2021 | | | | 8.6 |
|
||||||
|
| Apr 2021 | *Jan 2022* | | | | 21.04 Hirsute | 2.30.2 | 2.7.18 3.9.4 | 8.4 |
|
||||||
|
| Jun 2021 | | 2.32.0 |
|
||||||
|
| Aug 2021 | | 2.33.0 |
|
||||||
|
| Aug 2021 | | | | 8.7 |
|
||||||
|
| Aug 2021 | **Aug 2026** | | | | **Debian 11 Bullseye** | 2.30.2 | 2.7.18 3.9.2 | 8.4 |
|
||||||
|
| **Date** | **EOL** | **[Git][rel-g]** | **[Python][rel-p]** | **[SSH][rel-o]** | **[Ubuntu][rel-u] / [Debian][rel-d]** | **Git** | **Python** | **SSH** |
|
||||||
|
|
||||||
|
|
||||||
[contact]: ../README.md#contact
|
[contact]: ../README.md#contact
|
||||||
[rel-d]: https://en.wikipedia.org/wiki/Debian_version_history
|
[rel-d]: https://en.wikipedia.org/wiki/Debian_version_history
|
||||||
[rel-g]: https://en.wikipedia.org/wiki/Git#Releases
|
[rel-g]: https://en.wikipedia.org/wiki/Git#Releases
|
||||||
|
[rel-o]: https://www.openssh.com/releasenotes.html
|
||||||
[rel-p]: https://en.wikipedia.org/wiki/History_of_Python#Table_of_versions
|
[rel-p]: https://en.wikipedia.org/wiki/History_of_Python#Table_of_versions
|
||||||
[rel-u]: https://en.wikipedia.org/wiki/Ubuntu_version_history#Table_of_versions
|
[rel-u]: https://en.wikipedia.org/wiki/Ubuntu_version_history#Table_of_versions
|
||||||
[example announcement]: https://groups.google.com/d/topic/repo-discuss/UGBNismWo1M/discussion
|
[example announcement]: https://groups.google.com/d/topic/repo-discuss/UGBNismWo1M/discussion
|
||||||
|
45
fetch.py
Normal file
45
fetch.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Copyright (C) 2021 The Android Open Source Project
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""This module contains functions used to fetch files from various sources."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_file(url, verbose=False):
|
||||||
|
"""Fetch a file from the specified source using the appropriate protocol.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The contents of the file as bytes.
|
||||||
|
"""
|
||||||
|
scheme = urlparse(url).scheme
|
||||||
|
if scheme == 'gs':
|
||||||
|
cmd = ['gsutil', 'cat', url]
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
|
check=True)
|
||||||
|
if result.stderr and verbose:
|
||||||
|
print('warning: non-fatal error running "gsutil": %s' % result.stderr,
|
||||||
|
file=sys.stderr)
|
||||||
|
return result.stdout
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print('fatal: error running "gsutil": %s' % e.stderr,
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
with urlopen(url) as f:
|
||||||
|
return f.read()
|
@ -104,6 +104,10 @@ class GitConfig(object):
|
|||||||
os.path.dirname(self.file),
|
os.path.dirname(self.file),
|
||||||
'.repo_' + os.path.basename(self.file) + '.json')
|
'.repo_' + os.path.basename(self.file) + '.json')
|
||||||
|
|
||||||
|
def ClearCache(self):
|
||||||
|
"""Clear the in-memory cache of config."""
|
||||||
|
self._cache_dict = None
|
||||||
|
|
||||||
def Has(self, name, include_defaults=True):
|
def Has(self, name, include_defaults=True):
|
||||||
"""Return true if this configuration file has the key.
|
"""Return true if this configuration file has the key.
|
||||||
"""
|
"""
|
||||||
@ -349,7 +353,7 @@ class GitConfig(object):
|
|||||||
with open(self._json) as fd:
|
with open(self._json) as fd:
|
||||||
return json.load(fd)
|
return json.load(fd)
|
||||||
except (IOError, ValueError):
|
except (IOError, ValueError):
|
||||||
platform_utils.remove(self._json)
|
platform_utils.remove(self._json, missing_ok=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _SaveJson(self, cache):
|
def _SaveJson(self, cache):
|
||||||
@ -357,8 +361,7 @@ class GitConfig(object):
|
|||||||
with open(self._json, 'w') as fd:
|
with open(self._json, 'w') as fd:
|
||||||
json.dump(cache, fd, indent=2)
|
json.dump(cache, fd, indent=2)
|
||||||
except (IOError, TypeError):
|
except (IOError, TypeError):
|
||||||
if os.path.exists(self._json):
|
platform_utils.remove(self._json, missing_ok=True)
|
||||||
platform_utils.remove(self._json)
|
|
||||||
|
|
||||||
def _ReadGit(self):
|
def _ReadGit(self):
|
||||||
"""
|
"""
|
||||||
@ -368,9 +371,10 @@ class GitConfig(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
c = {}
|
c = {}
|
||||||
d = self._do('--null', '--list')
|
if not os.path.exists(self.file):
|
||||||
if d is None:
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
d = self._do('--null', '--list')
|
||||||
for line in d.rstrip('\0').split('\0'):
|
for line in d.rstrip('\0').split('\0'):
|
||||||
if '\n' in line:
|
if '\n' in line:
|
||||||
key, val = line.split('\n', 1)
|
key, val = line.split('\n', 1)
|
||||||
@ -399,7 +403,7 @@ class GitConfig(object):
|
|||||||
if p.Wait() == 0:
|
if p.Wait() == 0:
|
||||||
return p.stdout
|
return p.stdout
|
||||||
else:
|
else:
|
||||||
GitError('git config %s: %s' % (str(args), p.stderr))
|
raise GitError('git config %s: %s' % (str(args), p.stderr))
|
||||||
|
|
||||||
|
|
||||||
class RepoConfig(GitConfig):
|
class RepoConfig(GitConfig):
|
||||||
|
@ -90,7 +90,7 @@ class Superproject(object):
|
|||||||
self._git_event_log = git_event_log
|
self._git_event_log = git_event_log
|
||||||
self._quiet = quiet
|
self._quiet = quiet
|
||||||
self._print_messages = print_messages
|
self._print_messages = print_messages
|
||||||
self._branch = self._GetBranch()
|
self._branch = manifest.branch
|
||||||
self._repodir = os.path.abspath(repodir)
|
self._repodir = os.path.abspath(repodir)
|
||||||
self._superproject_dir = superproject_dir
|
self._superproject_dir = superproject_dir
|
||||||
self._superproject_path = os.path.join(self._repodir, superproject_dir)
|
self._superproject_path = os.path.join(self._repodir, superproject_dir)
|
||||||
@ -98,8 +98,12 @@ class Superproject(object):
|
|||||||
_SUPERPROJECT_MANIFEST_NAME)
|
_SUPERPROJECT_MANIFEST_NAME)
|
||||||
git_name = ''
|
git_name = ''
|
||||||
if self._manifest.superproject:
|
if self._manifest.superproject:
|
||||||
remote_name = self._manifest.superproject['remote'].name
|
remote = self._manifest.superproject['remote']
|
||||||
git_name = hashlib.md5(remote_name.encode('utf8')).hexdigest() + '-'
|
git_name = hashlib.md5(remote.name.encode('utf8')).hexdigest() + '-'
|
||||||
|
self._branch = self._manifest.superproject['revision']
|
||||||
|
self._remote_url = remote.url
|
||||||
|
else:
|
||||||
|
self._remote_url = None
|
||||||
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)
|
||||||
|
|
||||||
@ -113,30 +117,23 @@ class Superproject(object):
|
|||||||
"""Returns the manifest path if the path exists or None."""
|
"""Returns the manifest path if the path exists or None."""
|
||||||
return self._manifest_path if os.path.exists(self._manifest_path) else None
|
return self._manifest_path if os.path.exists(self._manifest_path) else None
|
||||||
|
|
||||||
def _GetBranch(self):
|
|
||||||
"""Returns the branch name for getting the approved manifest."""
|
|
||||||
p = self._manifest.manifestProject
|
|
||||||
b = p.GetBranch(p.CurrentBranch)
|
|
||||||
if not b:
|
|
||||||
return None
|
|
||||||
branch = b.merge
|
|
||||||
if branch and branch.startswith(R_HEADS):
|
|
||||||
branch = branch[len(R_HEADS):]
|
|
||||||
return branch
|
|
||||||
|
|
||||||
def _LogMessage(self, message):
|
def _LogMessage(self, message):
|
||||||
"""Logs message to stderr and _git_event_log."""
|
"""Logs message to stderr and _git_event_log."""
|
||||||
if self._print_messages:
|
if self._print_messages:
|
||||||
print(message, file=sys.stderr)
|
print(message, file=sys.stderr)
|
||||||
self._git_event_log.ErrorEvent(message, f'{message}')
|
self._git_event_log.ErrorEvent(message, f'{message}')
|
||||||
|
|
||||||
|
def _LogMessagePrefix(self):
|
||||||
|
"""Returns the prefix string to be logged in each log message"""
|
||||||
|
return f'repo superproject branch: {self._branch} url: {self._remote_url}'
|
||||||
|
|
||||||
def _LogError(self, message):
|
def _LogError(self, message):
|
||||||
"""Logs error message to stderr and _git_event_log."""
|
"""Logs error message to stderr and _git_event_log."""
|
||||||
self._LogMessage(f'repo superproject error: {message}')
|
self._LogMessage(f'{self._LogMessagePrefix()} error: {message}')
|
||||||
|
|
||||||
def _LogWarning(self, message):
|
def _LogWarning(self, message):
|
||||||
"""Logs warning message to stderr and _git_event_log."""
|
"""Logs warning message to stderr and _git_event_log."""
|
||||||
self._LogMessage(f'repo superproject warning: {message}')
|
self._LogMessage(f'{self._LogMessagePrefix()} warning: {message}')
|
||||||
|
|
||||||
def _Init(self):
|
def _Init(self):
|
||||||
"""Sets up a local Git repository to get a copy of a superproject.
|
"""Sets up a local Git repository to get a copy of a superproject.
|
||||||
@ -162,11 +159,8 @@ class Superproject(object):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _Fetch(self, url):
|
def _Fetch(self):
|
||||||
"""Fetches a local copy of a superproject for the manifest based on url.
|
"""Fetches a local copy of a superproject for the manifest based on |_remote_url|.
|
||||||
|
|
||||||
Args:
|
|
||||||
url: superproject's url.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if fetch is successful, or False.
|
True if fetch is successful, or False.
|
||||||
@ -177,7 +171,8 @@ class Superproject(object):
|
|||||||
if not git_require((2, 28, 0)):
|
if not git_require((2, 28, 0)):
|
||||||
self._LogWarning('superproject requires a git version 2.28 or later')
|
self._LogWarning('superproject requires a git version 2.28 or later')
|
||||||
return False
|
return False
|
||||||
cmd = ['fetch', url, '--depth', '1', '--force', '--no-tags', '--filter', 'blob:none']
|
cmd = ['fetch', self._remote_url, '--depth', '1', '--force', '--no-tags',
|
||||||
|
'--filter', 'blob:none']
|
||||||
if self._branch:
|
if self._branch:
|
||||||
cmd += [self._branch + ':' + self._branch]
|
cmd += [self._branch + ':' + self._branch]
|
||||||
p = GitCommand(None,
|
p = GitCommand(None,
|
||||||
@ -234,15 +229,14 @@ class Superproject(object):
|
|||||||
print('NOTICE: --use-superproject is in beta; report any issues to the '
|
print('NOTICE: --use-superproject is in beta; report any issues to the '
|
||||||
'address described in `repo version`', file=sys.stderr)
|
'address described in `repo version`', file=sys.stderr)
|
||||||
should_exit = True
|
should_exit = True
|
||||||
url = self._manifest.superproject['remote'].url
|
if not self._remote_url:
|
||||||
if not url:
|
|
||||||
self._LogWarning(f'superproject URL is not defined in manifest: '
|
self._LogWarning(f'superproject URL is not defined in manifest: '
|
||||||
f'{self._manifest.manifestFile}')
|
f'{self._manifest.manifestFile}')
|
||||||
return SyncResult(False, should_exit)
|
return SyncResult(False, should_exit)
|
||||||
|
|
||||||
if not self._Init():
|
if not self._Init():
|
||||||
return SyncResult(False, should_exit)
|
return SyncResult(False, should_exit)
|
||||||
if not self._Fetch(url):
|
if not self._Fetch():
|
||||||
return SyncResult(False, should_exit)
|
return SyncResult(False, should_exit)
|
||||||
if not self._quiet:
|
if not self._quiet:
|
||||||
print('%s: Initial setup for superproject completed.' % self._work_git)
|
print('%s: Initial setup for superproject completed.' % self._work_git)
|
||||||
@ -260,7 +254,7 @@ class Superproject(object):
|
|||||||
|
|
||||||
data = self._LsTree()
|
data = self._LsTree()
|
||||||
if not data:
|
if not data:
|
||||||
self._LogWarning(f'warning: git ls-tree failed to return data for manifest: '
|
self._LogWarning(f'git ls-tree failed to return data for manifest: '
|
||||||
f'{self._manifest.manifestFile}')
|
f'{self._manifest.manifestFile}')
|
||||||
return CommitIdsResult(None, True)
|
return CommitIdsResult(None, True)
|
||||||
|
|
||||||
@ -366,57 +360,36 @@ def _UseSuperprojectFromConfiguration():
|
|||||||
user_value = user_cfg.GetBoolean('repo.superprojectChoice')
|
user_value = user_cfg.GetBoolean('repo.superprojectChoice')
|
||||||
if user_value is not None:
|
if user_value is not None:
|
||||||
user_expiration = user_cfg.GetInt('repo.superprojectChoiceExpire')
|
user_expiration = user_cfg.GetInt('repo.superprojectChoiceExpire')
|
||||||
if user_expiration is not None and (user_expiration <= 0 or user_expiration >= time_now):
|
if user_expiration is None or user_expiration <= 0 or user_expiration >= time_now:
|
||||||
# TODO(b/190688390) - Remove prompt when we are comfortable with the new
|
# TODO(b/190688390) - Remove prompt when we are comfortable with the new
|
||||||
# default value.
|
# default value.
|
||||||
print(('You are currently enrolled in Git submodules experiment '
|
if user_value:
|
||||||
'(go/android-submodules-quickstart). Use --no-use-superproject '
|
print(('You are currently enrolled in Git submodules experiment '
|
||||||
'to override.\n'), file=sys.stderr)
|
'(go/android-submodules-quickstart). Use --no-use-superproject '
|
||||||
return user_value
|
'to override.\n'), file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(('You are not currently enrolled in Git submodules experiment '
|
||||||
|
'(go/android-submodules-quickstart). Use --use-superproject '
|
||||||
|
'to override.\n'), file=sys.stderr)
|
||||||
|
return user_value
|
||||||
|
|
||||||
# We don't have an unexpired choice, ask for one.
|
# We don't have an unexpired choice, ask for one.
|
||||||
system_cfg = RepoConfig.ForSystem()
|
system_cfg = RepoConfig.ForSystem()
|
||||||
system_value = system_cfg.GetBoolean('repo.superprojectChoice')
|
system_value = system_cfg.GetBoolean('repo.superprojectChoice')
|
||||||
if system_value:
|
if system_value:
|
||||||
# The system configuration is proposing that we should enable the
|
# The system configuration is proposing that we should enable the
|
||||||
# use of superproject. Present this to user for confirmation if we
|
# use of superproject. Treat the user as enrolled for two weeks.
|
||||||
# are on a TTY, or, when we are not on a TTY, accept the system
|
|
||||||
# default for this time only.
|
|
||||||
#
|
#
|
||||||
# TODO(b/190688390) - Remove prompt when we are comfortable with the new
|
# TODO(b/190688390) - Remove prompt when we are comfortable with the new
|
||||||
# default value.
|
# default value.
|
||||||
prompt = ('Repo can now use Git submodules (go/android-submodules-quickstart) '
|
userchoice = True
|
||||||
'instead of manifests to represent the state of the Android '
|
time_choiceexpire = time_now + (86400 * 14)
|
||||||
'superproject, which results in faster syncs and better atomicity.\n\n')
|
user_cfg.SetString('repo.superprojectChoiceExpire', str(time_choiceexpire))
|
||||||
if sys.stdout.isatty():
|
user_cfg.SetBoolean('repo.superprojectChoice', userchoice)
|
||||||
prompt += 'Would you like to opt in for two weeks (y/N)? '
|
print('You are automatically enrolled in Git submodules experiment '
|
||||||
response = input(prompt).lower()
|
'(go/android-submodules-quickstart) for another two weeks.\n',
|
||||||
time_choiceexpire = time_now + (86400 * 14)
|
file=sys.stderr)
|
||||||
if response in ('y', 'yes'):
|
return True
|
||||||
userchoice = True
|
|
||||||
elif response in ('a', 'always'):
|
|
||||||
userchoice = True
|
|
||||||
time_choiceexpire = 0
|
|
||||||
elif response == 'never':
|
|
||||||
userchoice = False
|
|
||||||
time_choiceexpire = 0
|
|
||||||
elif response in ('n', 'no'):
|
|
||||||
userchoice = False
|
|
||||||
else:
|
|
||||||
# Unrecognized user response, assume the intention was no, but
|
|
||||||
# only for 2 hours instead of 2 weeks to balance between not
|
|
||||||
# being overly pushy while still retain the opportunity to
|
|
||||||
# enroll.
|
|
||||||
userchoice = False
|
|
||||||
time_choiceexpire = time_now + 7200
|
|
||||||
|
|
||||||
user_cfg.SetString('repo.superprojectChoiceExpire', str(time_choiceexpire))
|
|
||||||
user_cfg.SetBoolean('repo.superprojectChoice', userchoice)
|
|
||||||
|
|
||||||
return userchoice
|
|
||||||
else:
|
|
||||||
print('Accepting once since we are not on a TTY', file=sys.stderr)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# For all other cases, we would not use superproject by default.
|
# For all other cases, we would not use superproject by default.
|
||||||
return False
|
return False
|
||||||
@ -437,4 +410,6 @@ def UseSuperproject(opt, manifest):
|
|||||||
if client_value is not None:
|
if client_value is not None:
|
||||||
return client_value
|
return client_value
|
||||||
else:
|
else:
|
||||||
|
if not manifest.superproject:
|
||||||
|
return False
|
||||||
return _UseSuperprojectFromConfiguration()
|
return _UseSuperprojectFromConfiguration()
|
||||||
|
@ -167,6 +167,26 @@ class EventLog(object):
|
|||||||
repo_config = {k: v for k, v in config.items() if k.startswith('repo.')}
|
repo_config = {k: v for k, v in config.items() if k.startswith('repo.')}
|
||||||
self.LogConfigEvents(repo_config, 'def_param')
|
self.LogConfigEvents(repo_config, 'def_param')
|
||||||
|
|
||||||
|
def GetDataEventName(self, value):
|
||||||
|
"""Returns 'data-json' if the value is an array else returns 'data'."""
|
||||||
|
return 'data-json' if value[0] == '[' and value[-1] == ']' else 'data'
|
||||||
|
|
||||||
|
def LogDataConfigEvents(self, config, prefix):
|
||||||
|
"""Append a 'data' event for each config key/value in |config| to the current log.
|
||||||
|
|
||||||
|
For each keyX and valueX of the config, "key" field of the event is '|prefix|/keyX'
|
||||||
|
and the "value" of the "key" field is valueX.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Configuration dictionary.
|
||||||
|
prefix: Prefix for each key that is logged.
|
||||||
|
"""
|
||||||
|
for key, value in config.items():
|
||||||
|
event = self._CreateEventDict(self.GetDataEventName(value))
|
||||||
|
event['key'] = f'{prefix}/{key}'
|
||||||
|
event['value'] = value
|
||||||
|
self._log.append(event)
|
||||||
|
|
||||||
def ErrorEvent(self, msg, fmt):
|
def ErrorEvent(self, msg, fmt):
|
||||||
"""Append a 'error' event to the current log."""
|
"""Append a 'error' event to the current log."""
|
||||||
error_event = self._CreateEventDict('error')
|
error_event = self._CreateEventDict('error')
|
||||||
|
@ -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-init" "Repo Manual"
|
.TH REPO "1" "November 2021" "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
|
||||||
@ -41,6 +41,10 @@ platform group [auto|all|none|linux|darwin|...]
|
|||||||
.TP
|
.TP
|
||||||
\fB\-\-submodules\fR
|
\fB\-\-submodules\fR
|
||||||
sync any submodules associated with the manifest repo
|
sync any submodules associated with the manifest repo
|
||||||
|
.TP
|
||||||
|
\fB\-\-standalone\-manifest\fR
|
||||||
|
download the manifest as a static file rather then
|
||||||
|
create a git checkout of the manifest repo
|
||||||
.SS Manifest (only) checkout options:
|
.SS Manifest (only) checkout options:
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-current\-branch\fR
|
\fB\-\-current\-branch\fR
|
||||||
@ -92,7 +96,8 @@ filter for use with \fB\-\-partial\-clone\fR [default:
|
|||||||
blob:none]
|
blob:none]
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-use\-superproject\fR
|
\fB\-\-use\-superproject\fR
|
||||||
use the manifest superproject to sync projects
|
use the manifest superproject to sync projects;
|
||||||
|
implies \fB\-c\fR
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-use\-superproject\fR
|
\fB\-\-no\-use\-superproject\fR
|
||||||
disable use of manifest superprojects
|
disable use of manifest superprojects
|
||||||
|
@ -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 init" "Repo Manual"
|
.TH REPO "1" "November 2021" "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
|
||||||
@ -41,6 +41,10 @@ platform group [auto|all|none|linux|darwin|...]
|
|||||||
.TP
|
.TP
|
||||||
\fB\-\-submodules\fR
|
\fB\-\-submodules\fR
|
||||||
sync any submodules associated with the manifest repo
|
sync any submodules associated with the manifest repo
|
||||||
|
.TP
|
||||||
|
\fB\-\-standalone\-manifest\fR
|
||||||
|
download the manifest as a static file rather then
|
||||||
|
create a git checkout of the manifest repo
|
||||||
.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
|
||||||
@ -92,7 +96,8 @@ filter for use with \fB\-\-partial\-clone\fR [default:
|
|||||||
blob:none]
|
blob:none]
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-use\-superproject\fR
|
\fB\-\-use\-superproject\fR
|
||||||
use the manifest superproject to sync projects
|
use the manifest superproject to sync projects;
|
||||||
|
implies \fB\-c\fR
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-use\-superproject\fR
|
\fB\-\-no\-use\-superproject\fR
|
||||||
disable use of manifest superprojects
|
disable use of manifest superprojects
|
||||||
@ -137,6 +142,12 @@ equivalent to using \fB\-b\fR HEAD.
|
|||||||
The optional \fB\-m\fR argument can be used to specify an alternate manifest to be
|
The optional \fB\-m\fR argument can be used to specify an alternate manifest to be
|
||||||
used. If no manifest is specified, the manifest default.xml will be used.
|
used. If no manifest is specified, the manifest default.xml will be used.
|
||||||
.PP
|
.PP
|
||||||
|
If the \fB\-\-standalone\-manifest\fR argument is set, the manifest will be downloaded
|
||||||
|
directly from the specified \fB\-\-manifest\-url\fR as a static file (rather than setting
|
||||||
|
up a manifest git checkout). With \fB\-\-standalone\-manifest\fR, the manifest will be
|
||||||
|
fully static and will not be re\-downloaded during subsesquent `repo init` and
|
||||||
|
`repo sync` calls.
|
||||||
|
.PP
|
||||||
The \fB\-\-reference\fR option can be used to point to a directory that has the content
|
The \fB\-\-reference\fR option can be used to point to a directory that has the content
|
||||||
of a \fB\-\-mirror\fR sync. This will make the working directory use as much data as
|
of a \fB\-\-mirror\fR sync. This will make the working directory use as much data as
|
||||||
possible from the local reference directory when fetching from the server. This
|
possible from the local reference directory when fetching from the server. This
|
||||||
|
@ -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 manifest" "Repo Manual"
|
.TH REPO "1" "November 2021" "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
|
||||||
@ -161,6 +161,7 @@ CDATA #IMPLIED>
|
|||||||
<!ELEMENT extend\-project EMPTY>
|
<!ELEMENT extend\-project EMPTY>
|
||||||
<!ATTLIST extend\-project name CDATA #REQUIRED>
|
<!ATTLIST extend\-project name CDATA #REQUIRED>
|
||||||
<!ATTLIST extend\-project path CDATA #IMPLIED>
|
<!ATTLIST extend\-project path CDATA #IMPLIED>
|
||||||
|
<!ATTLIST extend\-project dest\-path 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>
|
||||||
@ -174,8 +175,9 @@ CDATA #IMPLIED>
|
|||||||
<!ATTLIST repo\-hooks enabled\-list CDATA #REQUIRED>
|
<!ATTLIST repo\-hooks enabled\-list CDATA #REQUIRED>
|
||||||
.IP
|
.IP
|
||||||
<!ELEMENT superproject EMPTY>
|
<!ELEMENT superproject EMPTY>
|
||||||
<!ATTLIST superproject name CDATA #REQUIRED>
|
<!ATTLIST superproject name CDATA #REQUIRED>
|
||||||
<!ATTLIST superproject remote IDREF #IMPLIED>
|
<!ATTLIST superproject remote IDREF #IMPLIED>
|
||||||
|
<!ATTLIST superproject revision CDATA #IMPLIED>
|
||||||
.IP
|
.IP
|
||||||
<!ELEMENT contactinfo EMPTY>
|
<!ELEMENT contactinfo EMPTY>
|
||||||
<!ATTLIST contactinfo bugurl CDATA #REQUIRED>
|
<!ATTLIST contactinfo bugurl CDATA #REQUIRED>
|
||||||
@ -385,6 +387,11 @@ original manifest.
|
|||||||
Attribute `path`: If specified, limit the change to projects checked out at the
|
Attribute `path`: If specified, limit the change to projects checked out at the
|
||||||
specified path, rather than all projects with the given name.
|
specified path, rather than all projects with the given name.
|
||||||
.PP
|
.PP
|
||||||
|
Attribute `dest\-path`: If specified, a path relative to the top directory of the
|
||||||
|
repo client where the Git working directory for this project should be placed.
|
||||||
|
This is used to move a project in the checkout by overriding the existing `path`
|
||||||
|
setting.
|
||||||
|
.PP
|
||||||
Attribute `groups`: List of additional groups to which this project belongs.
|
Attribute `groups`: List of additional groups to which this project belongs.
|
||||||
Same syntax as the corresponding element of `project`.
|
Same syntax as the corresponding element of `project`.
|
||||||
.PP
|
.PP
|
||||||
@ -477,6 +484,10 @@ project](#element\-project) for more information.
|
|||||||
Attribute `remote`: Name of a previously defined remote element. If not supplied
|
Attribute `remote`: Name of a previously defined remote element. If not supplied
|
||||||
the remote given by the default element is used.
|
the remote given by the default element is used.
|
||||||
.PP
|
.PP
|
||||||
|
Attribute `revision`: Name of the Git branch the manifest wants to track for
|
||||||
|
this superproject. If not supplied the revision given by the remote element is
|
||||||
|
used if applicable, else the default element is used.
|
||||||
|
.PP
|
||||||
Element contactinfo
|
Element contactinfo
|
||||||
.PP
|
.PP
|
||||||
*** *Note*: This is currently a WIP. ***
|
*** *Note*: This is currently a WIP. ***
|
||||||
|
@ -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 smartsync" "Repo Manual"
|
.TH REPO "1" "November 2021" "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
|
||||||
@ -80,7 +80,8 @@ password to authenticate with the manifest server
|
|||||||
fetch submodules from server
|
fetch submodules from server
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-use\-superproject\fR
|
\fB\-\-use\-superproject\fR
|
||||||
use the manifest superproject to sync projects
|
use the manifest superproject to sync projects;
|
||||||
|
implies \fB\-c\fR
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-use\-superproject\fR
|
\fB\-\-no\-use\-superproject\fR
|
||||||
disable use of manifest superprojects
|
disable use of manifest superprojects
|
||||||
@ -89,7 +90,7 @@ disable use of manifest superprojects
|
|||||||
fetch tags
|
fetch tags
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-tags\fR
|
\fB\-\-no\-tags\fR
|
||||||
don't fetch tags
|
don't fetch tags (default)
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-optimized\-fetch\fR
|
\fB\-\-optimized\-fetch\fR
|
||||||
only fetch projects fixed to sha1 if revision does not
|
only fetch projects fixed to sha1 if revision does not
|
||||||
@ -100,6 +101,10 @@ number of times to retry fetches on transient errors
|
|||||||
.TP
|
.TP
|
||||||
\fB\-\-prune\fR
|
\fB\-\-prune\fR
|
||||||
delete refs that no longer exist on the remote
|
delete refs that no longer exist on the remote
|
||||||
|
(default)
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-prune\fR
|
||||||
|
do not delete refs that no longer exist on the remote
|
||||||
.SS Logging options:
|
.SS Logging options:
|
||||||
.TP
|
.TP
|
||||||
\fB\-v\fR, \fB\-\-verbose\fR
|
\fB\-v\fR, \fB\-\-verbose\fR
|
||||||
|
@ -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 sync" "Repo Manual"
|
.TH REPO "1" "November 2021" "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
|
||||||
@ -80,7 +80,8 @@ password to authenticate with the manifest server
|
|||||||
fetch submodules from server
|
fetch submodules from server
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-use\-superproject\fR
|
\fB\-\-use\-superproject\fR
|
||||||
use the manifest superproject to sync projects
|
use the manifest superproject to sync projects;
|
||||||
|
implies \fB\-c\fR
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-use\-superproject\fR
|
\fB\-\-no\-use\-superproject\fR
|
||||||
disable use of manifest superprojects
|
disable use of manifest superprojects
|
||||||
@ -89,7 +90,7 @@ disable use of manifest superprojects
|
|||||||
fetch tags
|
fetch tags
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-tags\fR
|
\fB\-\-no\-tags\fR
|
||||||
don't fetch tags
|
don't fetch tags (default)
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-optimized\-fetch\fR
|
\fB\-\-optimized\-fetch\fR
|
||||||
only fetch projects fixed to sha1 if revision does not
|
only fetch projects fixed to sha1 if revision does not
|
||||||
@ -100,6 +101,10 @@ number of times to retry fetches on transient errors
|
|||||||
.TP
|
.TP
|
||||||
\fB\-\-prune\fR
|
\fB\-\-prune\fR
|
||||||
delete refs that no longer exist on the remote
|
delete refs that no longer exist on the remote
|
||||||
|
(default)
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-prune\fR
|
||||||
|
do not delete refs that no longer exist on the remote
|
||||||
.TP
|
.TP
|
||||||
\fB\-s\fR, \fB\-\-smart\-sync\fR
|
\fB\-s\fR, \fB\-\-smart\-sync\fR
|
||||||
smart sync using manifest from the latest known good
|
smart sync using manifest from the latest known good
|
||||||
|
@ -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" "Repo Manual"
|
.TH REPO "1" "November 2021" "repo" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repository management tool built on top of git
|
repo \- repository management tool built on top of git
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -43,7 +43,7 @@ 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
|
||||||
.SS "The complete list of recognized repo commands are:"
|
.SS "The complete list of recognized repo commands is:"
|
||||||
.TP
|
.TP
|
||||||
abandon
|
abandon
|
||||||
Permanently abandon a development branch
|
Permanently abandon a development branch
|
||||||
|
@ -270,8 +270,7 @@ class XmlManifest(object):
|
|||||||
self.Override(name)
|
self.Override(name)
|
||||||
|
|
||||||
# Old versions of repo would generate symlinks we need to clean up.
|
# Old versions of repo would generate symlinks we need to clean up.
|
||||||
if os.path.lexists(self.manifestFile):
|
platform_utils.remove(self.manifestFile, missing_ok=True)
|
||||||
platform_utils.remove(self.manifestFile)
|
|
||||||
# This file is interpreted as if it existed inside the manifest repo.
|
# This file is interpreted as if it existed inside the manifest repo.
|
||||||
# That allows us to use <include> with the relative file name.
|
# That allows us to use <include> with the relative file name.
|
||||||
with open(self.manifestFile, 'w') as fp:
|
with open(self.manifestFile, 'w') as fp:
|
||||||
@ -507,6 +506,9 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
if not d.remote or remote.orig_name != remoteName:
|
if not d.remote or remote.orig_name != remoteName:
|
||||||
remoteName = remote.orig_name
|
remoteName = remote.orig_name
|
||||||
e.setAttribute('remote', remoteName)
|
e.setAttribute('remote', remoteName)
|
||||||
|
revision = remote.revision or d.revisionExpr
|
||||||
|
if not revision or revision != self._superproject['revision']:
|
||||||
|
e.setAttribute('revision', self._superproject['revision'])
|
||||||
root.appendChild(e)
|
root.appendChild(e)
|
||||||
|
|
||||||
if self._contactinfo.bugurl != Wrapper().BUG_URL:
|
if self._contactinfo.bugurl != Wrapper().BUG_URL:
|
||||||
@ -852,6 +854,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
for subproject in project.subprojects:
|
for subproject in project.subprojects:
|
||||||
recursively_add_projects(subproject)
|
recursively_add_projects(subproject)
|
||||||
|
|
||||||
|
repo_hooks_project = None
|
||||||
|
enabled_repo_hooks = None
|
||||||
for node in itertools.chain(*node_list):
|
for node in itertools.chain(*node_list):
|
||||||
if node.nodeName == 'project':
|
if node.nodeName == 'project':
|
||||||
project = self._ParseProject(node)
|
project = self._ParseProject(node)
|
||||||
@ -864,6 +868,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
'project: %s' % name)
|
'project: %s' % name)
|
||||||
|
|
||||||
path = node.getAttribute('path')
|
path = node.getAttribute('path')
|
||||||
|
dest_path = node.getAttribute('dest-path')
|
||||||
groups = node.getAttribute('groups')
|
groups = node.getAttribute('groups')
|
||||||
if groups:
|
if groups:
|
||||||
groups = self._ParseList(groups)
|
groups = self._ParseList(groups)
|
||||||
@ -872,46 +877,37 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
if remote:
|
if remote:
|
||||||
remote = self._get_remote(node)
|
remote = self._get_remote(node)
|
||||||
|
|
||||||
|
named_projects = self._projects[name]
|
||||||
|
if dest_path and not path and len(named_projects) > 1:
|
||||||
|
raise ManifestParseError('extend-project cannot use dest-path when '
|
||||||
|
'matching multiple projects: %s' % name)
|
||||||
for p in self._projects[name]:
|
for p in self._projects[name]:
|
||||||
if path and p.relpath != path:
|
if path and p.relpath != path:
|
||||||
continue
|
continue
|
||||||
if groups:
|
if groups:
|
||||||
p.groups.extend(groups)
|
p.groups.extend(groups)
|
||||||
if revision:
|
if revision:
|
||||||
p.revisionExpr = revision
|
p.SetRevision(revision)
|
||||||
if IsId(revision):
|
|
||||||
p.revisionId = revision
|
|
||||||
else:
|
|
||||||
p.revisionId = None
|
|
||||||
if remote:
|
if remote:
|
||||||
p.remote = remote.ToRemoteSpec(name)
|
p.remote = remote.ToRemoteSpec(name)
|
||||||
if node.nodeName == 'repo-hooks':
|
|
||||||
# Get the name of the project and the (space-separated) list of enabled.
|
|
||||||
repo_hooks_project = self._reqatt(node, 'in-project')
|
|
||||||
enabled_repo_hooks = self._ParseList(self._reqatt(node, 'enabled-list'))
|
|
||||||
|
|
||||||
|
if dest_path:
|
||||||
|
del self._paths[p.relpath]
|
||||||
|
relpath, worktree, gitdir, objdir, _ = self.GetProjectPaths(name, dest_path)
|
||||||
|
p.UpdatePaths(relpath, worktree, gitdir, objdir)
|
||||||
|
self._paths[p.relpath] = p
|
||||||
|
|
||||||
|
if node.nodeName == 'repo-hooks':
|
||||||
# Only one project can be the hooks project
|
# Only one project can be the hooks project
|
||||||
if self._repo_hooks_project is not None:
|
if repo_hooks_project is not None:
|
||||||
raise ManifestParseError(
|
raise ManifestParseError(
|
||||||
'duplicate repo-hooks in %s' %
|
'duplicate repo-hooks in %s' %
|
||||||
(self.manifestFile))
|
(self.manifestFile))
|
||||||
|
|
||||||
# Store a reference to the Project.
|
# Get the name of the project and the (space-separated) list of enabled.
|
||||||
try:
|
repo_hooks_project = self._reqatt(node, 'in-project')
|
||||||
repo_hooks_projects = self._projects[repo_hooks_project]
|
enabled_repo_hooks = self._ParseList(self._reqatt(node, 'enabled-list'))
|
||||||
except KeyError:
|
|
||||||
raise ManifestParseError(
|
|
||||||
'project %s not found for repo-hooks' %
|
|
||||||
(repo_hooks_project))
|
|
||||||
|
|
||||||
if len(repo_hooks_projects) != 1:
|
|
||||||
raise ManifestParseError(
|
|
||||||
'internal error parsing repo-hooks in %s' %
|
|
||||||
(self.manifestFile))
|
|
||||||
self._repo_hooks_project = repo_hooks_projects[0]
|
|
||||||
|
|
||||||
# Store the enabled hooks in the Project object.
|
|
||||||
self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
|
|
||||||
if node.nodeName == 'superproject':
|
if node.nodeName == 'superproject':
|
||||||
name = self._reqatt(node, 'name')
|
name = self._reqatt(node, 'name')
|
||||||
# There can only be one superproject.
|
# There can only be one superproject.
|
||||||
@ -929,6 +925,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
raise ManifestParseError("no remote for superproject %s within %s" %
|
raise ManifestParseError("no remote for superproject %s within %s" %
|
||||||
(name, self.manifestFile))
|
(name, self.manifestFile))
|
||||||
self._superproject['remote'] = remote.ToRemoteSpec(name)
|
self._superproject['remote'] = remote.ToRemoteSpec(name)
|
||||||
|
revision = node.getAttribute('revision') or remote.revision
|
||||||
|
if not revision:
|
||||||
|
revision = self._default.revisionExpr
|
||||||
|
if not revision:
|
||||||
|
raise ManifestParseError('no revision for superproject %s within %s' %
|
||||||
|
(name, self.manifestFile))
|
||||||
|
self._superproject['revision'] = revision
|
||||||
if node.nodeName == 'contactinfo':
|
if node.nodeName == 'contactinfo':
|
||||||
bugurl = self._reqatt(node, 'bugurl')
|
bugurl = self._reqatt(node, 'bugurl')
|
||||||
# This element can be repeated, later entries will clobber earlier ones.
|
# This element can be repeated, later entries will clobber earlier ones.
|
||||||
@ -944,12 +947,30 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
|
|
||||||
# If the manifest removes the hooks project, treat it as if it deleted
|
# If the manifest removes the hooks project, treat it as if it deleted
|
||||||
# the repo-hooks element too.
|
# the repo-hooks element too.
|
||||||
if self._repo_hooks_project and (self._repo_hooks_project.name == name):
|
if repo_hooks_project == name:
|
||||||
self._repo_hooks_project = None
|
repo_hooks_project = None
|
||||||
elif not XmlBool(node, 'optional', False):
|
elif not XmlBool(node, 'optional', False):
|
||||||
raise ManifestParseError('remove-project element specifies non-existent '
|
raise ManifestParseError('remove-project element specifies non-existent '
|
||||||
'project: %s' % name)
|
'project: %s' % name)
|
||||||
|
|
||||||
|
# Store repo hooks project information.
|
||||||
|
if repo_hooks_project:
|
||||||
|
# Store a reference to the Project.
|
||||||
|
try:
|
||||||
|
repo_hooks_projects = self._projects[repo_hooks_project]
|
||||||
|
except KeyError:
|
||||||
|
raise ManifestParseError(
|
||||||
|
'project %s not found for repo-hooks' %
|
||||||
|
(repo_hooks_project))
|
||||||
|
|
||||||
|
if len(repo_hooks_projects) != 1:
|
||||||
|
raise ManifestParseError(
|
||||||
|
'internal error parsing repo-hooks in %s' %
|
||||||
|
(self.manifestFile))
|
||||||
|
self._repo_hooks_project = repo_hooks_projects[0]
|
||||||
|
# Store the enabled hooks in the Project object.
|
||||||
|
self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
|
||||||
|
|
||||||
def _AddMetaProjectMirror(self, m):
|
def _AddMetaProjectMirror(self, m):
|
||||||
name = None
|
name = None
|
||||||
m_url = m.GetRemote(m.remote.name).url
|
m_url = m.GetRemote(m.remote.name).url
|
||||||
|
@ -124,31 +124,30 @@ def rename(src, dst):
|
|||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
os.rename(src, dst)
|
shutil.move(src, dst)
|
||||||
|
|
||||||
|
|
||||||
def remove(path):
|
def remove(path, missing_ok=False):
|
||||||
"""Remove (delete) the file path. This is a replacement for os.remove that
|
"""Remove (delete) the file path. This is a replacement for os.remove that
|
||||||
allows deleting read-only files on Windows, with support for long paths and
|
allows deleting read-only files on Windows, with support for long paths and
|
||||||
for deleting directory symbolic links.
|
for deleting directory symbolic links.
|
||||||
|
|
||||||
Availability: Unix, Windows."""
|
Availability: Unix, Windows."""
|
||||||
if isWindows():
|
longpath = _makelongpath(path) if isWindows() else path
|
||||||
longpath = _makelongpath(path)
|
try:
|
||||||
try:
|
os.remove(longpath)
|
||||||
os.remove(longpath)
|
except OSError as e:
|
||||||
except OSError as e:
|
if e.errno == errno.EACCES:
|
||||||
if e.errno == errno.EACCES:
|
os.chmod(longpath, stat.S_IWRITE)
|
||||||
os.chmod(longpath, stat.S_IWRITE)
|
# Directory symbolic links must be deleted with 'rmdir'.
|
||||||
# Directory symbolic links must be deleted with 'rmdir'.
|
if islink(longpath) and isdir(longpath):
|
||||||
if islink(longpath) and isdir(longpath):
|
os.rmdir(longpath)
|
||||||
os.rmdir(longpath)
|
|
||||||
else:
|
|
||||||
os.remove(longpath)
|
|
||||||
else:
|
else:
|
||||||
raise
|
os.remove(longpath)
|
||||||
else:
|
elif missing_ok and e.errno == errno.ENOENT:
|
||||||
os.remove(path)
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def walk(top, topdown=True, onerror=None, followlinks=False):
|
def walk(top, topdown=True, onerror=None, followlinks=False):
|
||||||
|
284
project.py
284
project.py
@ -457,11 +457,7 @@ 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_files = ['description', 'info']
|
shareable_dirs = ['hooks', 'objects', 'rr-cache']
|
||||||
shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
|
|
||||||
# These objects can only be used by a single working tree.
|
|
||||||
working_tree_files = ['config', 'packed-refs', 'shallow']
|
|
||||||
working_tree_dirs = ['logs', 'refs']
|
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
manifest,
|
manifest,
|
||||||
@ -519,21 +515,8 @@ class Project(object):
|
|||||||
self.client = self.manifest = manifest
|
self.client = self.manifest = manifest
|
||||||
self.name = name
|
self.name = name
|
||||||
self.remote = remote
|
self.remote = remote
|
||||||
self.gitdir = gitdir.replace('\\', '/')
|
self.UpdatePaths(relpath, worktree, gitdir, objdir)
|
||||||
self.objdir = objdir.replace('\\', '/')
|
self.SetRevision(revisionExpr, revisionId=revisionId)
|
||||||
if worktree:
|
|
||||||
self.worktree = os.path.normpath(worktree).replace('\\', '/')
|
|
||||||
else:
|
|
||||||
self.worktree = None
|
|
||||||
self.relpath = relpath
|
|
||||||
self.revisionExpr = revisionExpr
|
|
||||||
|
|
||||||
if revisionId is None \
|
|
||||||
and revisionExpr \
|
|
||||||
and IsId(revisionExpr):
|
|
||||||
self.revisionId = revisionExpr
|
|
||||||
else:
|
|
||||||
self.revisionId = revisionId
|
|
||||||
|
|
||||||
self.rebase = rebase
|
self.rebase = rebase
|
||||||
self.groups = groups
|
self.groups = groups
|
||||||
@ -556,16 +539,6 @@ class Project(object):
|
|||||||
self.copyfiles = []
|
self.copyfiles = []
|
||||||
self.linkfiles = []
|
self.linkfiles = []
|
||||||
self.annotations = []
|
self.annotations = []
|
||||||
self.config = GitConfig.ForRepository(gitdir=self.gitdir,
|
|
||||||
defaults=self.client.globalConfig)
|
|
||||||
|
|
||||||
if self.worktree:
|
|
||||||
self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
|
|
||||||
else:
|
|
||||||
self.work_git = None
|
|
||||||
self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
|
|
||||||
self.bare_ref = GitRefs(gitdir)
|
|
||||||
self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
|
|
||||||
self.dest_branch = dest_branch
|
self.dest_branch = dest_branch
|
||||||
self.old_revision = old_revision
|
self.old_revision = old_revision
|
||||||
|
|
||||||
@ -573,6 +546,35 @@ class Project(object):
|
|||||||
# project containing repo hooks.
|
# project containing repo hooks.
|
||||||
self.enabled_repo_hooks = []
|
self.enabled_repo_hooks = []
|
||||||
|
|
||||||
|
def SetRevision(self, revisionExpr, revisionId=None):
|
||||||
|
"""Set revisionId based on revision expression and id"""
|
||||||
|
self.revisionExpr = revisionExpr
|
||||||
|
if revisionId is None and revisionExpr and IsId(revisionExpr):
|
||||||
|
self.revisionId = self.revisionExpr
|
||||||
|
else:
|
||||||
|
self.revisionId = revisionId
|
||||||
|
|
||||||
|
def UpdatePaths(self, relpath, worktree, gitdir, objdir):
|
||||||
|
"""Update paths used by this project"""
|
||||||
|
self.gitdir = gitdir.replace('\\', '/')
|
||||||
|
self.objdir = objdir.replace('\\', '/')
|
||||||
|
if worktree:
|
||||||
|
self.worktree = os.path.normpath(worktree).replace('\\', '/')
|
||||||
|
else:
|
||||||
|
self.worktree = None
|
||||||
|
self.relpath = relpath
|
||||||
|
|
||||||
|
self.config = GitConfig.ForRepository(gitdir=self.gitdir,
|
||||||
|
defaults=self.manifest.globalConfig)
|
||||||
|
|
||||||
|
if self.worktree:
|
||||||
|
self.work_git = self._GitGetByExec(self, bare=False, gitdir=self.gitdir)
|
||||||
|
else:
|
||||||
|
self.work_git = None
|
||||||
|
self.bare_git = self._GitGetByExec(self, bare=True, gitdir=self.gitdir)
|
||||||
|
self.bare_ref = GitRefs(self.gitdir)
|
||||||
|
self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=self.objdir)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def Derived(self):
|
def Derived(self):
|
||||||
return self.is_derived
|
return self.is_derived
|
||||||
@ -1182,10 +1184,8 @@ class Project(object):
|
|||||||
self._InitMRef()
|
self._InitMRef()
|
||||||
else:
|
else:
|
||||||
self._InitMirrorHead()
|
self._InitMirrorHead()
|
||||||
try:
|
platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
|
||||||
platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
|
missing_ok=True)
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def PostRepoUpgrade(self):
|
def PostRepoUpgrade(self):
|
||||||
@ -2040,8 +2040,11 @@ class Project(object):
|
|||||||
|
|
||||||
if current_branch_only:
|
if current_branch_only:
|
||||||
if self.revisionExpr.startswith(R_TAGS):
|
if self.revisionExpr.startswith(R_TAGS):
|
||||||
# this is a tag and its sha1 value should never change
|
# This is a tag and its commit id should never change.
|
||||||
tag_name = self.revisionExpr[len(R_TAGS):]
|
tag_name = self.revisionExpr[len(R_TAGS):]
|
||||||
|
elif self.upstream and self.upstream.startswith(R_TAGS):
|
||||||
|
# This is a tag and its commit id should never change.
|
||||||
|
tag_name = self.upstream[len(R_TAGS):]
|
||||||
|
|
||||||
if is_sha1 or tag_name is not None:
|
if is_sha1 or tag_name is not None:
|
||||||
if self._CheckForImmutableRevision():
|
if self._CheckForImmutableRevision():
|
||||||
@ -2307,15 +2310,12 @@ class Project(object):
|
|||||||
cmd.append('+refs/tags/*:refs/tags/*')
|
cmd.append('+refs/tags/*:refs/tags/*')
|
||||||
|
|
||||||
ok = GitCommand(self, cmd, bare=True).Wait() == 0
|
ok = GitCommand(self, cmd, bare=True).Wait() == 0
|
||||||
if os.path.exists(bundle_dst):
|
platform_utils.remove(bundle_dst, missing_ok=True)
|
||||||
platform_utils.remove(bundle_dst)
|
platform_utils.remove(bundle_tmp, missing_ok=True)
|
||||||
if os.path.exists(bundle_tmp):
|
|
||||||
platform_utils.remove(bundle_tmp)
|
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
|
def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
|
||||||
if os.path.exists(dstPath):
|
platform_utils.remove(dstPath, missing_ok=True)
|
||||||
platform_utils.remove(dstPath)
|
|
||||||
|
|
||||||
cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
|
cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
|
||||||
if quiet:
|
if quiet:
|
||||||
@ -2439,7 +2439,7 @@ class Project(object):
|
|||||||
if quiet:
|
if quiet:
|
||||||
cmd.append('-q')
|
cmd.append('-q')
|
||||||
if GitCommand(self, cmd).Wait() != 0:
|
if GitCommand(self, cmd).Wait() != 0:
|
||||||
raise GitError('%s submodule update --init --recursive %s ' % self.name)
|
raise GitError('%s submodule update --init --recursive ' % self.name)
|
||||||
|
|
||||||
def _Rebase(self, upstream, onto=None):
|
def _Rebase(self, upstream, onto=None):
|
||||||
cmd = ['rebase']
|
cmd = ['rebase']
|
||||||
@ -2465,6 +2465,8 @@ class Project(object):
|
|||||||
os.makedirs(self.objdir)
|
os.makedirs(self.objdir)
|
||||||
self.bare_objdir.init()
|
self.bare_objdir.init()
|
||||||
|
|
||||||
|
self._UpdateHooks(quiet=quiet)
|
||||||
|
|
||||||
if self.use_git_worktrees:
|
if self.use_git_worktrees:
|
||||||
# Enable per-worktree config file support if possible. This is more a
|
# Enable per-worktree config file support if possible. This is more a
|
||||||
# nice-to-have feature for users rather than a hard requirement.
|
# nice-to-have feature for users rather than a hard requirement.
|
||||||
@ -2477,10 +2479,9 @@ class Project(object):
|
|||||||
os.makedirs(self.gitdir)
|
os.makedirs(self.gitdir)
|
||||||
|
|
||||||
if init_obj_dir or init_git_dir:
|
if init_obj_dir or init_git_dir:
|
||||||
self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
|
self._ReferenceGitDir(self.objdir, self.gitdir, copy_all=True)
|
||||||
copy_all=True)
|
|
||||||
try:
|
try:
|
||||||
self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
|
self._CheckDirReference(self.objdir, self.gitdir)
|
||||||
except GitError as e:
|
except GitError as e:
|
||||||
if force_sync:
|
if force_sync:
|
||||||
print("Retrying clone after deleting %s" %
|
print("Retrying clone after deleting %s" %
|
||||||
@ -2525,8 +2526,6 @@ class Project(object):
|
|||||||
_lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
|
_lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
|
||||||
os.path.join(ref_dir, 'objects') + '\n')
|
os.path.join(ref_dir, 'objects') + '\n')
|
||||||
|
|
||||||
self._UpdateHooks(quiet=quiet)
|
|
||||||
|
|
||||||
m = self.manifest.manifestProject.config
|
m = self.manifest.manifestProject.config
|
||||||
for key in ['user.name', 'user.email']:
|
for key in ['user.name', 'user.email']:
|
||||||
if m.Has(key, include_defaults=False):
|
if m.Has(key, include_defaults=False):
|
||||||
@ -2542,13 +2541,18 @@ class Project(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def _UpdateHooks(self, quiet=False):
|
def _UpdateHooks(self, quiet=False):
|
||||||
if os.path.exists(self.gitdir):
|
if os.path.exists(self.objdir):
|
||||||
self._InitHooks(quiet=quiet)
|
self._InitHooks(quiet=quiet)
|
||||||
|
|
||||||
def _InitHooks(self, quiet=False):
|
def _InitHooks(self, quiet=False):
|
||||||
hooks = platform_utils.realpath(self._gitdir_path('hooks'))
|
hooks = platform_utils.realpath(os.path.join(self.objdir, 'hooks'))
|
||||||
if not os.path.exists(hooks):
|
if not os.path.exists(hooks):
|
||||||
os.makedirs(hooks)
|
os.makedirs(hooks)
|
||||||
|
|
||||||
|
# Delete sample hooks. They're noise.
|
||||||
|
for hook in glob.glob(os.path.join(hooks, '*.sample')):
|
||||||
|
platform_utils.remove(hook, missing_ok=True)
|
||||||
|
|
||||||
for stock_hook in _ProjectHooks():
|
for stock_hook in _ProjectHooks():
|
||||||
name = os.path.basename(stock_hook)
|
name = os.path.basename(stock_hook)
|
||||||
|
|
||||||
@ -2646,40 +2650,16 @@ class Project(object):
|
|||||||
else:
|
else:
|
||||||
active_git.symbolic_ref('-m', msg, ref, dst)
|
active_git.symbolic_ref('-m', msg, ref, dst)
|
||||||
|
|
||||||
def _CheckDirReference(self, srcdir, destdir, share_refs):
|
def _CheckDirReference(self, srcdir, destdir):
|
||||||
# Git worktrees don't use symlinks to share at all.
|
# Git worktrees don't use symlinks to share at all.
|
||||||
if self.use_git_worktrees:
|
if self.use_git_worktrees:
|
||||||
return
|
return
|
||||||
|
|
||||||
symlink_files = self.shareable_files[:]
|
for name in self.shareable_dirs:
|
||||||
symlink_dirs = self.shareable_dirs[:]
|
|
||||||
if share_refs:
|
|
||||||
symlink_files += self.working_tree_files
|
|
||||||
symlink_dirs += self.working_tree_dirs
|
|
||||||
to_symlink = symlink_files + symlink_dirs
|
|
||||||
for name in set(to_symlink):
|
|
||||||
# Try to self-heal a bit in simple cases.
|
# Try to self-heal a bit in simple cases.
|
||||||
dst_path = os.path.join(destdir, name)
|
dst_path = os.path.join(destdir, name)
|
||||||
src_path = os.path.join(srcdir, name)
|
src_path = os.path.join(srcdir, name)
|
||||||
|
|
||||||
if name in self.working_tree_dirs:
|
|
||||||
# If the dir is missing under .repo/projects/, create it.
|
|
||||||
if not os.path.exists(src_path):
|
|
||||||
os.makedirs(src_path)
|
|
||||||
|
|
||||||
elif name in self.working_tree_files:
|
|
||||||
# If it's a file under the checkout .git/ and the .repo/projects/ has
|
|
||||||
# nothing, move the file under the .repo/projects/ tree.
|
|
||||||
if not os.path.exists(src_path) and os.path.isfile(dst_path):
|
|
||||||
platform_utils.rename(dst_path, src_path)
|
|
||||||
|
|
||||||
# If the path exists under the .repo/projects/ and there's no symlink
|
|
||||||
# under the checkout .git/, recreate the symlink.
|
|
||||||
if name in self.working_tree_dirs or name in self.working_tree_files:
|
|
||||||
if os.path.exists(src_path) and not os.path.exists(dst_path):
|
|
||||||
platform_utils.symlink(
|
|
||||||
os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
|
|
||||||
|
|
||||||
dst = platform_utils.realpath(dst_path)
|
dst = platform_utils.realpath(dst_path)
|
||||||
if os.path.lexists(dst):
|
if os.path.lexists(dst):
|
||||||
src = platform_utils.realpath(src_path)
|
src = platform_utils.realpath(src_path)
|
||||||
@ -2692,23 +2672,17 @@ class Project(object):
|
|||||||
' use `repo sync --force-sync {0}` to '
|
' use `repo sync --force-sync {0}` to '
|
||||||
'proceed.'.format(self.relpath))
|
'proceed.'.format(self.relpath))
|
||||||
|
|
||||||
def _ReferenceGitDir(self, gitdir, dotgit, share_refs, 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.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gitdir: The bare git repository. Must already be initialized.
|
gitdir: The bare git repository. Must already be initialized.
|
||||||
dotgit: The repository you would like to initialize.
|
dotgit: The repository you would like to initialize.
|
||||||
share_refs: If true, |dotgit| will store its refs under |gitdir|.
|
|
||||||
Only one work tree can store refs under a given |gitdir|.
|
|
||||||
copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
|
copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
|
||||||
This saves you the effort of initializing |dotgit| yourself.
|
This saves you the effort of initializing |dotgit| yourself.
|
||||||
"""
|
"""
|
||||||
symlink_files = self.shareable_files[:]
|
|
||||||
symlink_dirs = self.shareable_dirs[:]
|
symlink_dirs = self.shareable_dirs[:]
|
||||||
if share_refs:
|
to_symlink = symlink_dirs
|
||||||
symlink_files += self.working_tree_files
|
|
||||||
symlink_dirs += self.working_tree_dirs
|
|
||||||
to_symlink = symlink_files + symlink_dirs
|
|
||||||
|
|
||||||
to_copy = []
|
to_copy = []
|
||||||
if copy_all:
|
if copy_all:
|
||||||
@ -2736,14 +2710,6 @@ class Project(object):
|
|||||||
elif os.path.isfile(src):
|
elif os.path.isfile(src):
|
||||||
shutil.copy(src, dst)
|
shutil.copy(src, dst)
|
||||||
|
|
||||||
# If the source file doesn't exist, ensure the destination
|
|
||||||
# file doesn't either.
|
|
||||||
if name in symlink_files and not os.path.lexists(src):
|
|
||||||
try:
|
|
||||||
platform_utils.remove(dst)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == errno.EPERM:
|
if e.errno == errno.EPERM:
|
||||||
raise DownloadError(self._get_symlink_error_message())
|
raise DownloadError(self._get_symlink_error_message())
|
||||||
@ -2780,50 +2746,111 @@ class Project(object):
|
|||||||
self._InitMRef()
|
self._InitMRef()
|
||||||
|
|
||||||
def _InitWorkTree(self, force_sync=False, submodules=False):
|
def _InitWorkTree(self, force_sync=False, submodules=False):
|
||||||
realdotgit = os.path.join(self.worktree, '.git')
|
"""Setup the worktree .git path.
|
||||||
tmpdotgit = realdotgit + '.tmp'
|
|
||||||
init_dotgit = not os.path.exists(realdotgit)
|
This is the user-visible path like src/foo/.git/.
|
||||||
if init_dotgit:
|
|
||||||
if self.use_git_worktrees:
|
With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
|
||||||
|
With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
|
||||||
|
|
||||||
|
Older checkouts had .git/ directories. If we see that, migrate it.
|
||||||
|
|
||||||
|
This also handles changes in the manifest. Maybe this project was backed
|
||||||
|
by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
|
||||||
|
the path we point to under .repo/projects/ to match.
|
||||||
|
"""
|
||||||
|
dotgit = os.path.join(self.worktree, '.git')
|
||||||
|
|
||||||
|
# If using an old layout style (a directory), migrate it.
|
||||||
|
if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
|
||||||
|
self._MigrateOldWorkTreeGitDir(dotgit)
|
||||||
|
|
||||||
|
init_dotgit = not os.path.exists(dotgit)
|
||||||
|
if self.use_git_worktrees:
|
||||||
|
if init_dotgit:
|
||||||
self._InitGitWorktree()
|
self._InitGitWorktree()
|
||||||
self._CopyAndLinkFiles()
|
self._CopyAndLinkFiles()
|
||||||
return
|
|
||||||
|
|
||||||
dotgit = tmpdotgit
|
|
||||||
platform_utils.rmtree(tmpdotgit, ignore_errors=True)
|
|
||||||
os.makedirs(tmpdotgit)
|
|
||||||
self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
|
|
||||||
copy_all=False)
|
|
||||||
else:
|
else:
|
||||||
dotgit = realdotgit
|
if not init_dotgit:
|
||||||
|
# See if the project has changed.
|
||||||
|
if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
|
||||||
|
platform_utils.remove(dotgit)
|
||||||
|
|
||||||
try:
|
if init_dotgit or not os.path.exists(dotgit):
|
||||||
self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
|
os.makedirs(self.worktree, exist_ok=True)
|
||||||
except GitError as e:
|
platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
|
||||||
if force_sync and not init_dotgit:
|
|
||||||
try:
|
|
||||||
platform_utils.rmtree(dotgit)
|
|
||||||
return self._InitWorkTree(force_sync=False, submodules=submodules)
|
|
||||||
except Exception:
|
|
||||||
raise e
|
|
||||||
raise e
|
|
||||||
|
|
||||||
if init_dotgit:
|
if init_dotgit:
|
||||||
_lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
|
_lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
|
||||||
|
|
||||||
# Now that the .git dir is fully set up, move it to its final home.
|
# Finish checking out the worktree.
|
||||||
platform_utils.rename(tmpdotgit, realdotgit)
|
cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
|
||||||
|
if GitCommand(self, cmd).Wait() != 0:
|
||||||
|
raise GitError('Cannot initialize work tree for ' + self.name)
|
||||||
|
|
||||||
# Finish checking out the worktree.
|
if submodules:
|
||||||
cmd = ['read-tree', '--reset', '-u']
|
self._SyncSubmodules(quiet=True)
|
||||||
cmd.append('-v')
|
self._CopyAndLinkFiles()
|
||||||
cmd.append(HEAD)
|
|
||||||
if GitCommand(self, cmd).Wait() != 0:
|
|
||||||
raise GitError('Cannot initialize work tree for ' + self.name)
|
|
||||||
|
|
||||||
if submodules:
|
@classmethod
|
||||||
self._SyncSubmodules(quiet=True)
|
def _MigrateOldWorkTreeGitDir(cls, dotgit):
|
||||||
self._CopyAndLinkFiles()
|
"""Migrate the old worktree .git/ dir style to a symlink.
|
||||||
|
|
||||||
|
This logic specifically only uses state from |dotgit| to figure out where to
|
||||||
|
move content and not |self|. This way if the backing project also changed
|
||||||
|
places, we only do the .git/ dir to .git symlink migration here. The path
|
||||||
|
updates will happen independently.
|
||||||
|
"""
|
||||||
|
# Figure out where in .repo/projects/ it's pointing to.
|
||||||
|
if not os.path.islink(os.path.join(dotgit, 'refs')):
|
||||||
|
raise GitError(f'{dotgit}: unsupported checkout state')
|
||||||
|
gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
|
||||||
|
|
||||||
|
# Remove known symlink paths that exist in .repo/projects/.
|
||||||
|
KNOWN_LINKS = {
|
||||||
|
'config', 'description', 'hooks', 'info', 'logs', 'objects',
|
||||||
|
'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
|
||||||
|
}
|
||||||
|
# Paths that we know will be in both, but are safe to clobber in .repo/projects/.
|
||||||
|
SAFE_TO_CLOBBER = {
|
||||||
|
'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gitk.cache', 'index', 'ORIG_HEAD',
|
||||||
|
}
|
||||||
|
|
||||||
|
# First see if we'd succeed before starting the migration.
|
||||||
|
unknown_paths = []
|
||||||
|
for name in platform_utils.listdir(dotgit):
|
||||||
|
# Ignore all temporary/backup names. These are common with vim & emacs.
|
||||||
|
if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
dotgit_path = os.path.join(dotgit, name)
|
||||||
|
if name in KNOWN_LINKS:
|
||||||
|
if not platform_utils.islink(dotgit_path):
|
||||||
|
unknown_paths.append(f'{dotgit_path}: should be a symlink')
|
||||||
|
else:
|
||||||
|
gitdir_path = os.path.join(gitdir, name)
|
||||||
|
if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
|
||||||
|
unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
|
||||||
|
if unknown_paths:
|
||||||
|
raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
|
||||||
|
|
||||||
|
# Now walk the paths and sync the .git/ to .repo/projects/.
|
||||||
|
for name in platform_utils.listdir(dotgit):
|
||||||
|
dotgit_path = os.path.join(dotgit, name)
|
||||||
|
|
||||||
|
# Ignore all temporary/backup names. These are common with vim & emacs.
|
||||||
|
if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
|
||||||
|
platform_utils.remove(dotgit_path)
|
||||||
|
elif name in KNOWN_LINKS:
|
||||||
|
platform_utils.remove(dotgit_path)
|
||||||
|
else:
|
||||||
|
gitdir_path = os.path.join(gitdir, name)
|
||||||
|
platform_utils.remove(gitdir_path, missing_ok=True)
|
||||||
|
platform_utils.rename(dotgit_path, gitdir_path)
|
||||||
|
|
||||||
|
# Now that the dir should be empty, clear it out, and symlink it over.
|
||||||
|
platform_utils.rmdir(dotgit)
|
||||||
|
platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
|
||||||
|
|
||||||
def _get_symlink_error_message(self):
|
def _get_symlink_error_message(self):
|
||||||
if platform_utils.isWindows():
|
if platform_utils.isWindows():
|
||||||
@ -2833,9 +2860,6 @@ class Project(object):
|
|||||||
'for other options.')
|
'for other options.')
|
||||||
return 'filesystem must support symlinks'
|
return 'filesystem must support symlinks'
|
||||||
|
|
||||||
def _gitdir_path(self, path):
|
|
||||||
return platform_utils.realpath(os.path.join(self.gitdir, path))
|
|
||||||
|
|
||||||
def _revlist(self, *args, **kw):
|
def _revlist(self, *args, **kw):
|
||||||
a = []
|
a = []
|
||||||
a.extend(args)
|
a.extend(args)
|
||||||
|
@ -20,6 +20,7 @@ This is intended to be run only by the official Repo release managers.
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -49,18 +50,37 @@ def check(opts):
|
|||||||
util.run(opts, ['gpg', '--verify', f'{opts.launcher}.asc'])
|
util.run(opts, ['gpg', '--verify', f'{opts.launcher}.asc'])
|
||||||
|
|
||||||
|
|
||||||
def postmsg(opts):
|
def get_version(opts):
|
||||||
|
"""Get the version from |launcher|."""
|
||||||
|
# Make sure we don't search $PATH when signing the "repo" file in the cwd.
|
||||||
|
launcher = os.path.join('.', opts.launcher)
|
||||||
|
cmd = [launcher, '--version']
|
||||||
|
ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE)
|
||||||
|
m = re.search(r'repo launcher version ([0-9.]+)', ret.stdout)
|
||||||
|
if not m:
|
||||||
|
sys.exit(f'{opts.launcher}: unable to detect repo version')
|
||||||
|
return m.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def postmsg(opts, version):
|
||||||
"""Helpful info to show at the end for release manager."""
|
"""Helpful info to show at the end for release manager."""
|
||||||
print(f"""
|
print(f"""
|
||||||
Repo launcher bucket:
|
Repo launcher bucket:
|
||||||
gs://git-repo-downloads/
|
gs://git-repo-downloads/
|
||||||
|
|
||||||
To upload this launcher directly:
|
You should first upload it with a specific version:
|
||||||
gsutil cp -a public-read {opts.launcher} {opts.launcher}.asc gs://git-repo-downloads/
|
gsutil cp -a public-read {opts.launcher} gs://git-repo-downloads/repo-{version}
|
||||||
|
gsutil cp -a public-read {opts.launcher}.asc gs://git-repo-downloads/repo-{version}.asc
|
||||||
|
|
||||||
NB: You probably want to upload it with a specific version first, e.g.:
|
Then to make it the public default:
|
||||||
gsutil cp -a public-read {opts.launcher} gs://git-repo-downloads/repo-3.0
|
gsutil cp -a public-read gs://git-repo-downloads/repo-{version} gs://git-repo-downloads/repo
|
||||||
gsutil cp -a public-read {opts.launcher}.asc gs://git-repo-downloads/repo-3.0.asc
|
gsutil cp -a public-read gs://git-repo-downloads/repo-{version}.asc gs://git-repo-downloads/repo.asc
|
||||||
|
|
||||||
|
NB: If a rollback is necessary, the GS bucket archives old versions, and may be
|
||||||
|
accessed by specifying their unique id number.
|
||||||
|
gsutil ls -la gs://git-repo-downloads/repo gs://git-repo-downloads/repo.asc
|
||||||
|
gsutil cp -a public-read gs://git-repo-downloads/repo#<unique id> gs://git-repo-downloads/repo
|
||||||
|
gsutil cp -a public-read gs://git-repo-downloads/repo.asc#<unique id> gs://git-repo-downloads/repo.asc
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
@ -103,9 +123,10 @@ def main(argv):
|
|||||||
opts.keys = [util.KEYID_DSA, util.KEYID_RSA, util.KEYID_ECC]
|
opts.keys = [util.KEYID_DSA, util.KEYID_RSA, util.KEYID_ECC]
|
||||||
util.import_release_key(opts)
|
util.import_release_key(opts)
|
||||||
|
|
||||||
|
version = get_version(opts)
|
||||||
sign(opts)
|
sign(opts)
|
||||||
check(opts)
|
check(opts)
|
||||||
postmsg(opts)
|
postmsg(opts, version)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -59,11 +59,11 @@ 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'), TOPDIR.joinpath('repo'),
|
'-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), TOPDIR.joinpath('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'), TOPDIR.joinpath('repo'),
|
'-o', MANDIR.joinpath('repo.1.tmp'), TOPDIR.joinpath('repo'),
|
||||||
'-h', '--help-all'])
|
'-h', '--help-all'])
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tempdir:
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
@ -80,11 +80,23 @@ def main(argv):
|
|||||||
(r'^\.IP\n(.*:)\n', '.SS \g<1>\n'),
|
(r'^\.IP\n(.*:)\n', '.SS \g<1>\n'),
|
||||||
(r'^\.PP\nDescription', '.SH DETAILS'),
|
(r'^\.PP\nDescription', '.SH DETAILS'),
|
||||||
)
|
)
|
||||||
for path in MANDIR.glob('*.1'):
|
for tmp_path in MANDIR.glob('*.1.tmp'):
|
||||||
data = path.read_text()
|
path = tmp_path.parent / tmp_path.stem
|
||||||
|
old_data = path.read_text() if path.exists() else ''
|
||||||
|
|
||||||
|
data = tmp_path.read_text()
|
||||||
|
tmp_path.unlink()
|
||||||
|
|
||||||
for pattern, replacement in regex:
|
for pattern, replacement in regex:
|
||||||
data = re.sub(pattern, replacement, data, flags=re.M)
|
data = re.sub(pattern, replacement, data, flags=re.M)
|
||||||
path.write_text(data)
|
|
||||||
|
# If the only thing that changed was the date, don't refresh. This avoids
|
||||||
|
# a lot of noise when only one file actually updates.
|
||||||
|
old_data = re.sub(r'^(\.TH REPO "1" ")([^"]+)', r'\1', old_data, flags=re.M)
|
||||||
|
new_data = re.sub(r'^(\.TH REPO "1" ")([^"]+)', r'\1', data, flags=re.M)
|
||||||
|
if old_data != new_data:
|
||||||
|
path.write_text(data)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main(sys.argv[1:]))
|
sys.exit(main(sys.argv[1:]))
|
||||||
|
8
repo
8
repo
@ -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, 15)
|
VERSION = (2, 17)
|
||||||
|
|
||||||
# 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)
|
||||||
@ -312,6 +312,10 @@ def InitParser(parser, gitc_init=False):
|
|||||||
metavar='PLATFORM')
|
metavar='PLATFORM')
|
||||||
group.add_option('--submodules', action='store_true',
|
group.add_option('--submodules', action='store_true',
|
||||||
help='sync any submodules associated with the manifest repo')
|
help='sync any submodules associated with the manifest repo')
|
||||||
|
group.add_option('--standalone-manifest', action='store_true',
|
||||||
|
help='download the manifest as a static file '
|
||||||
|
'rather then create a git checkout of '
|
||||||
|
'the manifest repo')
|
||||||
|
|
||||||
# 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.
|
||||||
@ -368,7 +372,7 @@ def InitParser(parser, gitc_init=False):
|
|||||||
help='filter for use with --partial-clone '
|
help='filter for use with --partial-clone '
|
||||||
'[default: %default]')
|
'[default: %default]')
|
||||||
group.add_option('--use-superproject', action='store_true', default=None,
|
group.add_option('--use-superproject', action='store_true', default=None,
|
||||||
help='use the manifest superproject to sync projects')
|
help='use the manifest superproject to sync projects; implies -c')
|
||||||
group.add_option('--no-use-superproject', action='store_false',
|
group.add_option('--no-use-superproject', action='store_false',
|
||||||
dest='use_superproject',
|
dest='use_superproject',
|
||||||
help='disable use of manifest superprojects')
|
help='disable use of manifest superprojects')
|
||||||
|
3
ssh.py
3
ssh.py
@ -52,6 +52,9 @@ def version():
|
|||||||
"""return ssh version as a tuple"""
|
"""return ssh version as a tuple"""
|
||||||
try:
|
try:
|
||||||
return _parse_ssh_version()
|
return _parse_ssh_version()
|
||||||
|
except FileNotFoundError:
|
||||||
|
print('fatal: ssh not installed', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print('fatal: unable to detect ssh version', file=sys.stderr)
|
print('fatal: unable to detect ssh version', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -53,7 +53,7 @@ Displays detailed usage information about a command.
|
|||||||
self.PrintAllCommandsBody()
|
self.PrintAllCommandsBody()
|
||||||
|
|
||||||
def PrintAllCommandsBody(self):
|
def PrintAllCommandsBody(self):
|
||||||
print('The complete list of recognized repo commands are:')
|
print('The complete list of recognized repo commands is:')
|
||||||
commandNames = list(sorted(all_commands))
|
commandNames = list(sorted(all_commands))
|
||||||
self._PrintCommands(commandNames)
|
self._PrintCommands(commandNames)
|
||||||
print("See 'repo help <command>' for more information on a "
|
print("See 'repo help <command>' for more information on a "
|
||||||
|
@ -24,6 +24,7 @@ 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 git_superproject
|
import git_superproject
|
||||||
import platform_utils
|
import platform_utils
|
||||||
from wrapper import Wrapper
|
from wrapper import Wrapper
|
||||||
@ -53,6 +54,12 @@ The optional -m argument can be used to specify an alternate manifest
|
|||||||
to be used. If no manifest is specified, the manifest default.xml
|
to be used. If no manifest is specified, the manifest default.xml
|
||||||
will be used.
|
will be used.
|
||||||
|
|
||||||
|
If the --standalone-manifest argument is set, the manifest will be downloaded
|
||||||
|
directly from the specified --manifest-url as a static file (rather than
|
||||||
|
setting up a manifest git checkout). With --standalone-manifest, the manifest
|
||||||
|
will be fully static and will not be re-downloaded during subsesquent
|
||||||
|
`repo init` and `repo sync` calls.
|
||||||
|
|
||||||
The --reference option can be used to point to a directory that
|
The --reference option can be used to point to a directory that
|
||||||
has the content of a --mirror sync. This will make the working
|
has the content of a --mirror sync. This will make the working
|
||||||
directory use as much data as possible from the local reference
|
directory use as much data as possible from the local reference
|
||||||
@ -112,6 +119,27 @@ to update the working directory files.
|
|||||||
m = self.manifest.manifestProject
|
m = self.manifest.manifestProject
|
||||||
is_new = not m.Exists
|
is_new = not m.Exists
|
||||||
|
|
||||||
|
# If repo has already been initialized, we take -u with the absence of
|
||||||
|
# --standalone-manifest to mean "transition to a standard repo set up",
|
||||||
|
# which necessitates starting fresh.
|
||||||
|
# If --standalone-manifest is set, we always tear everything down and start
|
||||||
|
# anew.
|
||||||
|
if not is_new:
|
||||||
|
was_standalone_manifest = m.config.GetString('manifest.standalone')
|
||||||
|
if was_standalone_manifest and not opt.manifest_url:
|
||||||
|
print('fatal: repo was initialized with a standlone manifest, '
|
||||||
|
'cannot be re-initialized without --manifest-url/-u')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if opt.standalone_manifest or (was_standalone_manifest and
|
||||||
|
opt.manifest_url):
|
||||||
|
m.config.ClearCache()
|
||||||
|
if m.gitdir and os.path.exists(m.gitdir):
|
||||||
|
platform_utils.rmtree(m.gitdir)
|
||||||
|
if m.worktree and os.path.exists(m.worktree):
|
||||||
|
platform_utils.rmtree(m.worktree)
|
||||||
|
|
||||||
|
is_new = not m.Exists
|
||||||
if is_new:
|
if is_new:
|
||||||
if not opt.manifest_url:
|
if not opt.manifest_url:
|
||||||
print('fatal: manifest url is required.', file=sys.stderr)
|
print('fatal: manifest url is required.', file=sys.stderr)
|
||||||
@ -136,6 +164,21 @@ to update the working directory files.
|
|||||||
|
|
||||||
m._InitGitDir(mirror_git=mirrored_manifest_git)
|
m._InitGitDir(mirror_git=mirrored_manifest_git)
|
||||||
|
|
||||||
|
# If standalone_manifest is set, mark the project as "standalone" -- we'll
|
||||||
|
# still do much of the manifests.git set up, but will avoid actual syncs to
|
||||||
|
# a remote.
|
||||||
|
standalone_manifest = False
|
||||||
|
if opt.standalone_manifest:
|
||||||
|
standalone_manifest = True
|
||||||
|
m.config.SetString('manifest.standalone', opt.manifest_url)
|
||||||
|
elif not opt.manifest_url and not opt.manifest_branch:
|
||||||
|
# If -u is set and --standalone-manifest is not, then we're not in
|
||||||
|
# standalone mode. Otherwise, use config to infer what we were in the last
|
||||||
|
# init.
|
||||||
|
standalone_manifest = bool(m.config.GetString('manifest.standalone'))
|
||||||
|
if not standalone_manifest:
|
||||||
|
m.config.SetString('manifest.standalone', None)
|
||||||
|
|
||||||
self._ConfigureDepth(opt)
|
self._ConfigureDepth(opt)
|
||||||
|
|
||||||
# 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.
|
||||||
@ -145,22 +188,23 @@ to update the working directory files.
|
|||||||
r.ResetFetch()
|
r.ResetFetch()
|
||||||
r.Save()
|
r.Save()
|
||||||
|
|
||||||
if opt.manifest_branch:
|
if not standalone_manifest:
|
||||||
if opt.manifest_branch == 'HEAD':
|
if opt.manifest_branch:
|
||||||
opt.manifest_branch = m.ResolveRemoteHead()
|
if opt.manifest_branch == 'HEAD':
|
||||||
if opt.manifest_branch is None:
|
opt.manifest_branch = m.ResolveRemoteHead()
|
||||||
print('fatal: unable to resolve HEAD', file=sys.stderr)
|
if opt.manifest_branch is None:
|
||||||
sys.exit(1)
|
print('fatal: unable to resolve HEAD', file=sys.stderr)
|
||||||
m.revisionExpr = opt.manifest_branch
|
sys.exit(1)
|
||||||
else:
|
m.revisionExpr = opt.manifest_branch
|
||||||
if is_new:
|
|
||||||
default_branch = m.ResolveRemoteHead()
|
|
||||||
if default_branch is None:
|
|
||||||
# If the remote doesn't have HEAD configured, default to master.
|
|
||||||
default_branch = 'refs/heads/master'
|
|
||||||
m.revisionExpr = default_branch
|
|
||||||
else:
|
else:
|
||||||
m.PreSync()
|
if is_new:
|
||||||
|
default_branch = m.ResolveRemoteHead()
|
||||||
|
if default_branch is None:
|
||||||
|
# If the remote doesn't have HEAD configured, default to master.
|
||||||
|
default_branch = 'refs/heads/master'
|
||||||
|
m.revisionExpr = default_branch
|
||||||
|
else:
|
||||||
|
m.PreSync()
|
||||||
|
|
||||||
groups = re.split(r'[,\s]+', opt.groups)
|
groups = re.split(r'[,\s]+', opt.groups)
|
||||||
all_platforms = ['linux', 'darwin', 'windows']
|
all_platforms = ['linux', 'darwin', 'windows']
|
||||||
@ -250,6 +294,16 @@ to update the working directory files.
|
|||||||
if opt.use_superproject is not None:
|
if opt.use_superproject is not None:
|
||||||
m.config.SetBoolean('repo.superproject', opt.use_superproject)
|
m.config.SetBoolean('repo.superproject', opt.use_superproject)
|
||||||
|
|
||||||
|
if standalone_manifest:
|
||||||
|
if is_new:
|
||||||
|
manifest_name = 'default.xml'
|
||||||
|
manifest_data = fetch.fetch_file(opt.manifest_url, verbose=opt.verbose)
|
||||||
|
dest = os.path.join(m.worktree, manifest_name)
|
||||||
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
|
with open(dest, 'wb') as f:
|
||||||
|
f.write(manifest_data)
|
||||||
|
return
|
||||||
|
|
||||||
if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet, verbose=opt.verbose,
|
if not m.Sync_NetworkHalf(is_new=is_new, quiet=opt.quiet, verbose=opt.verbose,
|
||||||
clone_bundle=opt.clone_bundle,
|
clone_bundle=opt.clone_bundle,
|
||||||
current_branch_only=opt.current_branch_only,
|
current_branch_only=opt.current_branch_only,
|
||||||
@ -423,8 +477,18 @@ to update the working directory files.
|
|||||||
|
|
||||||
# Check this here, else manifest will be tagged "not new" and init won't be
|
# Check this here, else manifest will be tagged "not new" and init won't be
|
||||||
# possible anymore without removing the .repo/manifests directory.
|
# possible anymore without removing the .repo/manifests directory.
|
||||||
if opt.archive and opt.mirror:
|
if opt.mirror:
|
||||||
self.OptionParser.error('--mirror and --archive cannot be used together.')
|
if opt.archive:
|
||||||
|
self.OptionParser.error('--mirror and --archive cannot be used '
|
||||||
|
'together.')
|
||||||
|
if opt.use_superproject is not None:
|
||||||
|
self.OptionParser.error('--mirror and --use-superproject cannot be '
|
||||||
|
'used together.')
|
||||||
|
|
||||||
|
if opt.standalone_manifest and (opt.manifest_branch or
|
||||||
|
opt.manifest_name != 'default.xml'):
|
||||||
|
self.OptionParser.error('--manifest-branch and --manifest-name cannot'
|
||||||
|
' be used with --standalone-manifest.')
|
||||||
|
|
||||||
if args:
|
if args:
|
||||||
if opt.manifest_url:
|
if opt.manifest_url:
|
||||||
|
106
subcmds/sync.py
106
subcmds/sync.py
@ -12,7 +12,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import errno
|
|
||||||
import functools
|
import functools
|
||||||
import http.cookiejar as cookielib
|
import http.cookiejar as cookielib
|
||||||
import io
|
import io
|
||||||
@ -235,24 +234,25 @@ later is required to fix a server side protocol bug.
|
|||||||
dest='fetch_submodules', action='store_true',
|
dest='fetch_submodules', action='store_true',
|
||||||
help='fetch submodules from server')
|
help='fetch submodules from server')
|
||||||
p.add_option('--use-superproject', action='store_true',
|
p.add_option('--use-superproject', action='store_true',
|
||||||
help='use the manifest superproject to sync projects')
|
help='use the manifest superproject to sync projects; implies -c')
|
||||||
p.add_option('--no-use-superproject', action='store_false',
|
p.add_option('--no-use-superproject', action='store_false',
|
||||||
dest='use_superproject',
|
dest='use_superproject',
|
||||||
help='disable use of manifest superprojects')
|
help='disable use of manifest superprojects')
|
||||||
p.add_option('--tags',
|
p.add_option('--tags', action='store_true',
|
||||||
action='store_false',
|
|
||||||
help='fetch tags')
|
help='fetch tags')
|
||||||
p.add_option('--no-tags',
|
p.add_option('--no-tags',
|
||||||
dest='tags', action='store_false',
|
dest='tags', action='store_false',
|
||||||
help="don't fetch tags")
|
help="don't fetch tags (default)")
|
||||||
p.add_option('--optimized-fetch',
|
p.add_option('--optimized-fetch',
|
||||||
dest='optimized_fetch', action='store_true',
|
dest='optimized_fetch', action='store_true',
|
||||||
help='only fetch projects fixed to sha1 if revision does not exist locally')
|
help='only fetch projects fixed to sha1 if revision does not exist locally')
|
||||||
p.add_option('--retry-fetches',
|
p.add_option('--retry-fetches',
|
||||||
default=0, action='store', type='int',
|
default=0, action='store', type='int',
|
||||||
help='number of times to retry fetches on transient errors')
|
help='number of times to retry fetches on transient errors')
|
||||||
p.add_option('--prune', dest='prune', action='store_true',
|
p.add_option('--prune', action='store_true',
|
||||||
help='delete refs that no longer exist on the remote')
|
help='delete refs that no longer exist on the remote (default)')
|
||||||
|
p.add_option('--no-prune', dest='prune', action='store_false',
|
||||||
|
help='do not delete refs that no longer exist on the remote')
|
||||||
if show_smart:
|
if show_smart:
|
||||||
p.add_option('-s', '--smart-sync',
|
p.add_option('-s', '--smart-sync',
|
||||||
dest='smart_sync', action='store_true',
|
dest='smart_sync', action='store_true',
|
||||||
@ -448,8 +448,8 @@ later is required to fix a server side protocol bug.
|
|||||||
else:
|
else:
|
||||||
pm.update(inc=0, msg='warming up')
|
pm.update(inc=0, msg='warming up')
|
||||||
chunksize = 4
|
chunksize = 4
|
||||||
with multiprocessing.Pool(
|
with multiprocessing.Pool(jobs, initializer=self._FetchInitChild,
|
||||||
jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)) as pool:
|
initargs=(ssh_proxy,)) as pool:
|
||||||
results = pool.imap_unordered(
|
results = pool.imap_unordered(
|
||||||
functools.partial(self._FetchProjectList, opt),
|
functools.partial(self._FetchProjectList, opt),
|
||||||
projects_list,
|
projects_list,
|
||||||
@ -605,7 +605,7 @@ later is required to fix a server side protocol bug.
|
|||||||
pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
|
pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
|
||||||
pm.update(inc=0, msg='prescan')
|
pm.update(inc=0, msg='prescan')
|
||||||
|
|
||||||
gc_gitdirs = {}
|
tidy_dirs = {}
|
||||||
for project in projects:
|
for project in projects:
|
||||||
# Make sure pruning never kicks in with shared projects.
|
# Make sure pruning never kicks in with shared projects.
|
||||||
if (not project.use_git_worktrees and
|
if (not project.use_git_worktrees and
|
||||||
@ -622,17 +622,29 @@ later is required to fix a server side protocol bug.
|
|||||||
% (project.relpath,),
|
% (project.relpath,),
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
project.config.SetString('gc.pruneExpire', 'never')
|
project.config.SetString('gc.pruneExpire', 'never')
|
||||||
gc_gitdirs[project.gitdir] = project.bare_git
|
project.config.SetString('gc.autoDetach', 'false')
|
||||||
|
# Only call git gc once per objdir, but call pack-refs for the remainder.
|
||||||
pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
|
if project.objdir not in tidy_dirs:
|
||||||
|
tidy_dirs[project.objdir] = (
|
||||||
|
True, # Run a full gc.
|
||||||
|
project.bare_git,
|
||||||
|
)
|
||||||
|
elif project.gitdir not in tidy_dirs:
|
||||||
|
tidy_dirs[project.gitdir] = (
|
||||||
|
False, # Do not run a full gc; just run pack-refs.
|
||||||
|
project.bare_git,
|
||||||
|
)
|
||||||
|
|
||||||
cpu_count = os.cpu_count()
|
cpu_count = os.cpu_count()
|
||||||
jobs = min(self.jobs, cpu_count)
|
jobs = min(self.jobs, cpu_count)
|
||||||
|
|
||||||
if jobs < 2:
|
if jobs < 2:
|
||||||
for bare_git in gc_gitdirs.values():
|
for (run_gc, bare_git) in tidy_dirs.values():
|
||||||
pm.update(msg=bare_git._project.name)
|
pm.update(msg=bare_git._project.name)
|
||||||
bare_git.gc('--auto')
|
if run_gc:
|
||||||
|
bare_git.gc('--auto')
|
||||||
|
else:
|
||||||
|
bare_git.pack_refs()
|
||||||
pm.end()
|
pm.end()
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -641,11 +653,14 @@ later is required to fix a server side protocol bug.
|
|||||||
threads = set()
|
threads = set()
|
||||||
sem = _threading.Semaphore(jobs)
|
sem = _threading.Semaphore(jobs)
|
||||||
|
|
||||||
def GC(bare_git):
|
def tidy_up(run_gc, bare_git):
|
||||||
pm.start(bare_git._project.name)
|
pm.start(bare_git._project.name)
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
bare_git.gc('--auto', config=config)
|
if run_gc:
|
||||||
|
bare_git.gc('--auto', config=config)
|
||||||
|
else:
|
||||||
|
bare_git.pack_refs(config=config)
|
||||||
except GitError:
|
except GitError:
|
||||||
err_event.set()
|
err_event.set()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -655,11 +670,11 @@ later is required to fix a server side protocol bug.
|
|||||||
pm.finish(bare_git._project.name)
|
pm.finish(bare_git._project.name)
|
||||||
sem.release()
|
sem.release()
|
||||||
|
|
||||||
for bare_git in gc_gitdirs.values():
|
for (run_gc, bare_git) in tidy_dirs.values():
|
||||||
if err_event.is_set() and opt.fail_fast:
|
if err_event.is_set() and opt.fail_fast:
|
||||||
break
|
break
|
||||||
sem.acquire()
|
sem.acquire()
|
||||||
t = _threading.Thread(target=GC, args=(bare_git,))
|
t = _threading.Thread(target=tidy_up, args=(run_gc, bare_git,))
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
threads.add(t)
|
threads.add(t)
|
||||||
t.start()
|
t.start()
|
||||||
@ -752,7 +767,7 @@ later is required to fix a server side protocol bug.
|
|||||||
with open(copylinkfile_path, 'rb') as fp:
|
with open(copylinkfile_path, 'rb') as fp:
|
||||||
try:
|
try:
|
||||||
old_copylinkfile_paths = json.load(fp)
|
old_copylinkfile_paths = json.load(fp)
|
||||||
except:
|
except Exception:
|
||||||
print('error: %s is not a json formatted file.' %
|
print('error: %s is not a json formatted file.' %
|
||||||
copylinkfile_path, file=sys.stderr)
|
copylinkfile_path, file=sys.stderr)
|
||||||
platform_utils.remove(copylinkfile_path)
|
platform_utils.remove(copylinkfile_path)
|
||||||
@ -767,13 +782,9 @@ later is required to fix a server side protocol bug.
|
|||||||
set(new_copyfile_paths))
|
set(new_copyfile_paths))
|
||||||
|
|
||||||
for need_remove_file in need_remove_files:
|
for need_remove_file in need_remove_files:
|
||||||
try:
|
# Try to remove the updated copyfile or linkfile.
|
||||||
platform_utils.remove(need_remove_file)
|
# So, if the file is not exist, nothing need to do.
|
||||||
except OSError as e:
|
platform_utils.remove(need_remove_file, missing_ok=True)
|
||||||
if e.errno == errno.ENOENT:
|
|
||||||
# Try to remove the updated copyfile or linkfile.
|
|
||||||
# So, if the file is not exist, nothing need to do.
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
|
# Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
|
||||||
with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
|
with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
|
||||||
@ -918,6 +929,9 @@ later is required to fix a server side protocol bug.
|
|||||||
if None in [opt.manifest_server_username, opt.manifest_server_password]:
|
if None in [opt.manifest_server_username, opt.manifest_server_password]:
|
||||||
self.OptionParser.error('both -u and -p must be given')
|
self.OptionParser.error('both -u and -p must be given')
|
||||||
|
|
||||||
|
if opt.prune is None:
|
||||||
|
opt.prune = True
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
if opt.jobs:
|
if opt.jobs:
|
||||||
self.jobs = opt.jobs
|
self.jobs = opt.jobs
|
||||||
@ -958,18 +972,25 @@ later is required to fix a server side protocol bug.
|
|||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
|
|
||||||
mp = self.manifest.manifestProject
|
mp = self.manifest.manifestProject
|
||||||
mp.PreSync()
|
is_standalone_manifest = mp.config.GetString('manifest.standalone')
|
||||||
|
if not is_standalone_manifest:
|
||||||
|
mp.PreSync()
|
||||||
|
|
||||||
if opt.repo_upgraded:
|
if opt.repo_upgraded:
|
||||||
_PostRepoUpgrade(self.manifest, quiet=opt.quiet)
|
_PostRepoUpgrade(self.manifest, quiet=opt.quiet)
|
||||||
|
|
||||||
if not opt.mp_update:
|
if not opt.mp_update:
|
||||||
print('Skipping update of local manifest project.')
|
print('Skipping update of local manifest project.')
|
||||||
else:
|
elif not is_standalone_manifest:
|
||||||
self._UpdateManifestProject(opt, mp, manifest_name)
|
self._UpdateManifestProject(opt, mp, manifest_name)
|
||||||
|
|
||||||
load_local_manifests = not self.manifest.HasLocalManifests
|
load_local_manifests = not self.manifest.HasLocalManifests
|
||||||
use_superproject = git_superproject.UseSuperproject(opt, self.manifest)
|
use_superproject = git_superproject.UseSuperproject(opt, self.manifest)
|
||||||
|
if use_superproject and (self.manifest.IsMirror or self.manifest.IsArchive):
|
||||||
|
# Don't use superproject, because we have no working tree.
|
||||||
|
use_superproject = False
|
||||||
|
if opt.use_superproject is not None:
|
||||||
|
print('Defaulting to no-use-superproject because there is no working tree.')
|
||||||
superproject_logging_data = {
|
superproject_logging_data = {
|
||||||
'superproject': use_superproject,
|
'superproject': use_superproject,
|
||||||
'haslocalmanifests': bool(self.manifest.HasLocalManifests),
|
'haslocalmanifests': bool(self.manifest.HasLocalManifests),
|
||||||
@ -1092,19 +1113,28 @@ later is required to fix a server side protocol bug.
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Log the previous sync analysis state from the config.
|
# Log the previous sync analysis state from the config.
|
||||||
self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
|
self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(),
|
||||||
'previous_sync_state')
|
'previous_sync_state')
|
||||||
|
|
||||||
# Update and log with the new sync analysis state.
|
# Update and log with the new sync analysis state.
|
||||||
mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
|
mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
|
||||||
self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
|
self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(),
|
||||||
'current_sync_state')
|
'current_sync_state')
|
||||||
|
|
||||||
if not opt.quiet:
|
if not opt.quiet:
|
||||||
print('repo sync has finished successfully.')
|
print('repo sync has finished successfully.')
|
||||||
|
|
||||||
|
|
||||||
def _PostRepoUpgrade(manifest, quiet=False):
|
def _PostRepoUpgrade(manifest, quiet=False):
|
||||||
|
# Link the docs for the internal .repo/ layout for people
|
||||||
|
link = os.path.join(manifest.repodir, 'internal-fs-layout.md')
|
||||||
|
if not platform_utils.islink(link):
|
||||||
|
target = os.path.join('repo', 'docs', 'internal-fs-layout.md')
|
||||||
|
try:
|
||||||
|
platform_utils.symlink(target, link)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
if wrapper.NeedSetupGnuPG():
|
if wrapper.NeedSetupGnuPG():
|
||||||
wrapper.SetupGnuPG(quiet)
|
wrapper.SetupGnuPG(quiet)
|
||||||
@ -1171,10 +1201,7 @@ class _FetchTimes(object):
|
|||||||
with open(self._path) as f:
|
with open(self._path) as f:
|
||||||
self._times = json.load(f)
|
self._times = json.load(f)
|
||||||
except (IOError, ValueError):
|
except (IOError, ValueError):
|
||||||
try:
|
platform_utils.remove(self._path, missing_ok=True)
|
||||||
platform_utils.remove(self._path)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
self._times = {}
|
self._times = {}
|
||||||
|
|
||||||
def Save(self):
|
def Save(self):
|
||||||
@ -1192,10 +1219,7 @@ class _FetchTimes(object):
|
|||||||
with open(self._path, 'w') as f:
|
with open(self._path, 'w') as f:
|
||||||
json.dump(self._times, f, indent=2)
|
json.dump(self._times, f, indent=2)
|
||||||
except (IOError, TypeError):
|
except (IOError, TypeError):
|
||||||
try:
|
platform_utils.remove(self._path, missing_ok=True)
|
||||||
platform_utils.remove(self._path)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# This is a replacement for xmlrpc.client.Transport using urllib2
|
# This is a replacement for xmlrpc.client.Transport using urllib2
|
||||||
# and supporting persistent-http[s]. It cannot change hosts from
|
# and supporting persistent-http[s]. It cannot change hosts from
|
||||||
|
10
tests/fixtures/test.gitconfig
vendored
10
tests/fixtures/test.gitconfig
vendored
@ -11,13 +11,3 @@
|
|||||||
intk = 10k
|
intk = 10k
|
||||||
intm = 10m
|
intm = 10m
|
||||||
intg = 10g
|
intg = 10g
|
||||||
[repo "syncstate.main"]
|
|
||||||
synctime = 2021-08-13T18:37:43.928600Z
|
|
||||||
version = 1
|
|
||||||
[repo "syncstate.sys"]
|
|
||||||
argv = ['/usr/bin/pytest-3']
|
|
||||||
[repo "syncstate.superproject"]
|
|
||||||
test = false
|
|
||||||
[repo "syncstate.options"]
|
|
||||||
verbose = true
|
|
||||||
mpupdate = false
|
|
||||||
|
@ -104,25 +104,6 @@ class GitConfigReadOnlyTests(unittest.TestCase):
|
|||||||
for key, value in TESTS:
|
for key, value in TESTS:
|
||||||
self.assertEqual(value, self.config.GetInt('section.%s' % (key,)))
|
self.assertEqual(value, self.config.GetInt('section.%s' % (key,)))
|
||||||
|
|
||||||
def test_GetSyncAnalysisStateData(self):
|
|
||||||
"""Test config entries with a sync state analysis data."""
|
|
||||||
superproject_logging_data = {}
|
|
||||||
superproject_logging_data['test'] = False
|
|
||||||
options = type('options', (object,), {})()
|
|
||||||
options.verbose = 'true'
|
|
||||||
options.mp_update = 'false'
|
|
||||||
TESTS = (
|
|
||||||
('superproject.test', 'false'),
|
|
||||||
('options.verbose', 'true'),
|
|
||||||
('options.mpupdate', 'false'),
|
|
||||||
('main.version', '1'),
|
|
||||||
)
|
|
||||||
self.config.UpdateSyncAnalysisState(options, superproject_logging_data)
|
|
||||||
sync_data = self.config.GetSyncAnalysisStateData()
|
|
||||||
for key, value in TESTS:
|
|
||||||
self.assertEqual(sync_data[f'{git_config.SYNC_STATE_PREFIX}{key}'], value)
|
|
||||||
self.assertTrue(sync_data[f'{git_config.SYNC_STATE_PREFIX}main.synctime'])
|
|
||||||
|
|
||||||
|
|
||||||
class GitConfigReadWriteTests(unittest.TestCase):
|
class GitConfigReadWriteTests(unittest.TestCase):
|
||||||
"""Read/write tests of the GitConfig class."""
|
"""Read/write tests of the GitConfig class."""
|
||||||
@ -187,6 +168,25 @@ class GitConfigReadWriteTests(unittest.TestCase):
|
|||||||
config = self.get_config()
|
config = self.get_config()
|
||||||
self.assertIsNone(config.GetBoolean('foo.bar'))
|
self.assertIsNone(config.GetBoolean('foo.bar'))
|
||||||
|
|
||||||
|
def test_GetSyncAnalysisStateData(self):
|
||||||
|
"""Test config entries with a sync state analysis data."""
|
||||||
|
superproject_logging_data = {}
|
||||||
|
superproject_logging_data['test'] = False
|
||||||
|
options = type('options', (object,), {})()
|
||||||
|
options.verbose = 'true'
|
||||||
|
options.mp_update = 'false'
|
||||||
|
TESTS = (
|
||||||
|
('superproject.test', 'false'),
|
||||||
|
('options.verbose', 'true'),
|
||||||
|
('options.mpupdate', 'false'),
|
||||||
|
('main.version', '1'),
|
||||||
|
)
|
||||||
|
self.config.UpdateSyncAnalysisState(options, superproject_logging_data)
|
||||||
|
sync_data = self.config.GetSyncAnalysisStateData()
|
||||||
|
for key, value in TESTS:
|
||||||
|
self.assertEqual(sync_data[f'{git_config.SYNC_STATE_PREFIX}{key}'], value)
|
||||||
|
self.assertTrue(sync_data[f'{git_config.SYNC_STATE_PREFIX}main.synctime'])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -157,7 +157,7 @@ class SuperprojectTestCase(unittest.TestCase):
|
|||||||
""")
|
""")
|
||||||
self._superproject = git_superproject.Superproject(manifest, self.repodir,
|
self._superproject = git_superproject.Superproject(manifest, self.repodir,
|
||||||
self.git_event_log)
|
self.git_event_log)
|
||||||
with mock.patch.object(self._superproject, '_GetBranch', return_value='junk'):
|
with mock.patch.object(self._superproject, '_branch', 'junk'):
|
||||||
sync_result = self._superproject.Sync()
|
sync_result = self._superproject.Sync()
|
||||||
self.assertFalse(sync_result.success)
|
self.assertFalse(sync_result.success)
|
||||||
self.assertTrue(sync_result.fatal)
|
self.assertTrue(sync_result.fatal)
|
||||||
|
@ -42,7 +42,7 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
self._event_log_module = git_trace2_event_log.EventLog(env=env)
|
self._event_log_module = git_trace2_event_log.EventLog(env=env)
|
||||||
self._log_data = None
|
self._log_data = None
|
||||||
|
|
||||||
def verifyCommonKeys(self, log_entry, expected_event_name, full_sid=True):
|
def verifyCommonKeys(self, log_entry, expected_event_name=None, full_sid=True):
|
||||||
"""Helper function to verify common event log keys."""
|
"""Helper function to verify common event log keys."""
|
||||||
self.assertIn('event', log_entry)
|
self.assertIn('event', log_entry)
|
||||||
self.assertIn('sid', log_entry)
|
self.assertIn('sid', log_entry)
|
||||||
@ -50,7 +50,8 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
self.assertIn('time', log_entry)
|
self.assertIn('time', log_entry)
|
||||||
|
|
||||||
# Do basic data format validation.
|
# Do basic data format validation.
|
||||||
self.assertEqual(expected_event_name, log_entry['event'])
|
if expected_event_name:
|
||||||
|
self.assertEqual(expected_event_name, log_entry['event'])
|
||||||
if full_sid:
|
if full_sid:
|
||||||
self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX)
|
self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX)
|
||||||
else:
|
else:
|
||||||
@ -65,6 +66,13 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
log_data.append(json.loads(line))
|
log_data.append(json.loads(line))
|
||||||
return log_data
|
return log_data
|
||||||
|
|
||||||
|
def remove_prefix(self, s, prefix):
|
||||||
|
"""Return a copy string after removing |prefix| from |s|, if present or the original string."""
|
||||||
|
if s.startswith(prefix):
|
||||||
|
return s[len(prefix):]
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
def test_initial_state_with_parent_sid(self):
|
def test_initial_state_with_parent_sid(self):
|
||||||
"""Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent."""
|
"""Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent."""
|
||||||
self.assertRegex(self._event_log_module.full_sid, self.FULL_SID_REGEX)
|
self.assertRegex(self._event_log_module.full_sid, self.FULL_SID_REGEX)
|
||||||
@ -234,6 +242,42 @@ class EventLogTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(len(self._log_data), 1)
|
self.assertEqual(len(self._log_data), 1)
|
||||||
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
|
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
|
||||||
|
|
||||||
|
def test_data_event_config(self):
|
||||||
|
"""Test 'data' event data outputs all config keys.
|
||||||
|
|
||||||
|
Expected event log:
|
||||||
|
<version event>
|
||||||
|
<data event>
|
||||||
|
<data event>
|
||||||
|
"""
|
||||||
|
config = {
|
||||||
|
'git.foo': 'bar',
|
||||||
|
'repo.partialclone': 'false',
|
||||||
|
'repo.syncstate.superproject.hassuperprojecttag': 'true',
|
||||||
|
'repo.syncstate.superproject.sys.argv': ['--', 'sync', 'protobuf'],
|
||||||
|
}
|
||||||
|
prefix_value = 'prefix'
|
||||||
|
self._event_log_module.LogDataConfigEvents(config, prefix_value)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
|
||||||
|
log_path = self._event_log_module.Write(path=tempdir)
|
||||||
|
self._log_data = self.readLog(log_path)
|
||||||
|
|
||||||
|
self.assertEqual(len(self._log_data), 5)
|
||||||
|
data_events = self._log_data[1:]
|
||||||
|
self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
|
||||||
|
|
||||||
|
for event in data_events:
|
||||||
|
self.verifyCommonKeys(event)
|
||||||
|
# Check for 'data' event specific fields.
|
||||||
|
self.assertIn('key', event)
|
||||||
|
self.assertIn('value', event)
|
||||||
|
key = event['key']
|
||||||
|
key = self.remove_prefix(key, f'{prefix_value}/')
|
||||||
|
value = event['value']
|
||||||
|
self.assertEqual(self._event_log_module.GetDataEventName(value), event['event'])
|
||||||
|
self.assertTrue(key in config and value == config[key])
|
||||||
|
|
||||||
def test_error_event(self):
|
def test_error_event(self):
|
||||||
"""Test and validate 'error' event data is valid.
|
"""Test and validate 'error' event data is valid.
|
||||||
|
|
||||||
|
@ -261,6 +261,19 @@ class XmlManifestTests(ManifestParseTestCase):
|
|||||||
<project name="repohooks" path="src/repohooks"/>
|
<project name="repohooks" path="src/repohooks"/>
|
||||||
<repo-hooks in-project="repohooks" enabled-list="a, b"/>
|
<repo-hooks in-project="repohooks" enabled-list="a, b"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
|
||||||
|
self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
|
||||||
|
|
||||||
|
def test_repo_hooks_unordered(self):
|
||||||
|
"""Check repo-hooks settings work even if the project def comes second."""
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="test-remote" fetch="http://localhost" />
|
||||||
|
<default remote="test-remote" revision="refs/heads/main" />
|
||||||
|
<repo-hooks in-project="repohooks" enabled-list="a, b"/>
|
||||||
|
<project name="repohooks" path="src/repohooks"/>
|
||||||
|
</manifest>
|
||||||
""")
|
""")
|
||||||
self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
|
self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
|
||||||
self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
|
self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
|
||||||
@ -559,6 +572,7 @@ class SuperProjectElementTests(ManifestParseTestCase):
|
|||||||
self.assertEqual(manifest.superproject['name'], 'superproject')
|
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||||
self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
|
self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
|
||||||
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
|
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
|
||||||
|
self.assertEqual(manifest.superproject['revision'], 'refs/heads/main')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sort_attributes(manifest.ToXml().toxml()),
|
sort_attributes(manifest.ToXml().toxml()),
|
||||||
'<?xml version="1.0" ?><manifest>'
|
'<?xml version="1.0" ?><manifest>'
|
||||||
@ -567,6 +581,72 @@ class SuperProjectElementTests(ManifestParseTestCase):
|
|||||||
'<superproject name="superproject"/>'
|
'<superproject name="superproject"/>'
|
||||||
'</manifest>')
|
'</manifest>')
|
||||||
|
|
||||||
|
def test_superproject_revision(self):
|
||||||
|
"""Check superproject settings with a different revision attribute"""
|
||||||
|
self.maxDiff = None
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="test-remote" fetch="http://localhost" />
|
||||||
|
<default remote="test-remote" revision="refs/heads/main" />
|
||||||
|
<superproject name="superproject" revision="refs/heads/stable" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||||
|
self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
|
||||||
|
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
|
||||||
|
self.assertEqual(manifest.superproject['revision'], 'refs/heads/stable')
|
||||||
|
self.assertEqual(
|
||||||
|
sort_attributes(manifest.ToXml().toxml()),
|
||||||
|
'<?xml version="1.0" ?><manifest>'
|
||||||
|
'<remote fetch="http://localhost" name="test-remote"/>'
|
||||||
|
'<default remote="test-remote" revision="refs/heads/main"/>'
|
||||||
|
'<superproject name="superproject" revision="refs/heads/stable"/>'
|
||||||
|
'</manifest>')
|
||||||
|
|
||||||
|
def test_superproject_revision_default_negative(self):
|
||||||
|
"""Check superproject settings with a same revision attribute"""
|
||||||
|
self.maxDiff = None
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="test-remote" fetch="http://localhost" />
|
||||||
|
<default remote="test-remote" revision="refs/heads/stable" />
|
||||||
|
<superproject name="superproject" revision="refs/heads/stable" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||||
|
self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
|
||||||
|
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
|
||||||
|
self.assertEqual(manifest.superproject['revision'], 'refs/heads/stable')
|
||||||
|
self.assertEqual(
|
||||||
|
sort_attributes(manifest.ToXml().toxml()),
|
||||||
|
'<?xml version="1.0" ?><manifest>'
|
||||||
|
'<remote fetch="http://localhost" name="test-remote"/>'
|
||||||
|
'<default remote="test-remote" revision="refs/heads/stable"/>'
|
||||||
|
'<superproject name="superproject"/>'
|
||||||
|
'</manifest>')
|
||||||
|
|
||||||
|
def test_superproject_revision_remote(self):
|
||||||
|
"""Check superproject settings with a same revision attribute"""
|
||||||
|
self.maxDiff = None
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" />
|
||||||
|
<default remote="test-remote" />
|
||||||
|
<superproject name="superproject" revision="refs/heads/stable" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||||
|
self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
|
||||||
|
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
|
||||||
|
self.assertEqual(manifest.superproject['revision'], 'refs/heads/stable')
|
||||||
|
self.assertEqual(
|
||||||
|
sort_attributes(manifest.ToXml().toxml()),
|
||||||
|
'<?xml version="1.0" ?><manifest>'
|
||||||
|
'<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>'
|
||||||
|
'<default remote="test-remote"/>'
|
||||||
|
'<superproject name="superproject" revision="refs/heads/stable"/>'
|
||||||
|
'</manifest>')
|
||||||
|
|
||||||
def test_remote(self):
|
def test_remote(self):
|
||||||
"""Check superproject settings with a remote."""
|
"""Check superproject settings with a remote."""
|
||||||
manifest = self.getXmlManifest("""
|
manifest = self.getXmlManifest("""
|
||||||
@ -580,6 +660,7 @@ class SuperProjectElementTests(ManifestParseTestCase):
|
|||||||
self.assertEqual(manifest.superproject['name'], 'platform/superproject')
|
self.assertEqual(manifest.superproject['name'], 'platform/superproject')
|
||||||
self.assertEqual(manifest.superproject['remote'].name, 'superproject-remote')
|
self.assertEqual(manifest.superproject['remote'].name, 'superproject-remote')
|
||||||
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/platform/superproject')
|
self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/platform/superproject')
|
||||||
|
self.assertEqual(manifest.superproject['revision'], 'refs/heads/main')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sort_attributes(manifest.ToXml().toxml()),
|
sort_attributes(manifest.ToXml().toxml()),
|
||||||
'<?xml version="1.0" ?><manifest>'
|
'<?xml version="1.0" ?><manifest>'
|
||||||
@ -600,6 +681,7 @@ class SuperProjectElementTests(ManifestParseTestCase):
|
|||||||
""")
|
""")
|
||||||
self.assertEqual(manifest.superproject['name'], 'superproject')
|
self.assertEqual(manifest.superproject['name'], 'superproject')
|
||||||
self.assertEqual(manifest.superproject['remote'].name, 'default-remote')
|
self.assertEqual(manifest.superproject['remote'].name, 'default-remote')
|
||||||
|
self.assertEqual(manifest.superproject['revision'], 'refs/heads/main')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sort_attributes(manifest.ToXml().toxml()),
|
sort_attributes(manifest.ToXml().toxml()),
|
||||||
'<?xml version="1.0" ?><manifest>'
|
'<?xml version="1.0" ?><manifest>'
|
||||||
@ -715,3 +797,49 @@ class RemoveProjectElementTests(ManifestParseTestCase):
|
|||||||
</manifest>
|
</manifest>
|
||||||
""")
|
""")
|
||||||
self.assertEqual(manifest.projects, [])
|
self.assertEqual(manifest.projects, [])
|
||||||
|
|
||||||
|
|
||||||
|
class ExtendProjectElementTests(ManifestParseTestCase):
|
||||||
|
"""Tests for <extend-project>."""
|
||||||
|
|
||||||
|
def test_extend_project_dest_path_single_match(self):
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="default-remote" fetch="http://localhost" />
|
||||||
|
<default remote="default-remote" revision="refs/heads/main" />
|
||||||
|
<project name="myproject" />
|
||||||
|
<extend-project name="myproject" dest-path="bar" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(len(manifest.projects), 1)
|
||||||
|
self.assertEqual(manifest.projects[0].relpath, 'bar')
|
||||||
|
|
||||||
|
def test_extend_project_dest_path_multi_match(self):
|
||||||
|
with self.assertRaises(manifest_xml.ManifestParseError):
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="default-remote" fetch="http://localhost" />
|
||||||
|
<default remote="default-remote" revision="refs/heads/main" />
|
||||||
|
<project name="myproject" path="x" />
|
||||||
|
<project name="myproject" path="y" />
|
||||||
|
<extend-project name="myproject" dest-path="bar" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
manifest.projects
|
||||||
|
|
||||||
|
def test_extend_project_dest_path_multi_match_path_specified(self):
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="default-remote" fetch="http://localhost" />
|
||||||
|
<default remote="default-remote" revision="refs/heads/main" />
|
||||||
|
<project name="myproject" path="x" />
|
||||||
|
<project name="myproject" path="y" />
|
||||||
|
<extend-project name="myproject" path="x" dest-path="bar" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(len(manifest.projects), 2)
|
||||||
|
if manifest.projects[0].relpath == 'y':
|
||||||
|
self.assertEqual(manifest.projects[1].relpath, 'bar')
|
||||||
|
else:
|
||||||
|
self.assertEqual(manifest.projects[0].relpath, 'bar')
|
||||||
|
self.assertEqual(manifest.projects[1].relpath, 'y')
|
||||||
|
50
tests/test_platform_utils.py
Normal file
50
tests/test_platform_utils.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright 2021 The Android Open Source Project
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Unittests for the platform_utils.py module."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import platform_utils
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveTests(unittest.TestCase):
|
||||||
|
"""Check remove() helper."""
|
||||||
|
|
||||||
|
def testMissingOk(self):
|
||||||
|
"""Check missing_ok handling."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
path = os.path.join(tmpdir, 'test')
|
||||||
|
|
||||||
|
# Should not fail.
|
||||||
|
platform_utils.remove(path, missing_ok=True)
|
||||||
|
|
||||||
|
# Should fail.
|
||||||
|
self.assertRaises(OSError, platform_utils.remove, path)
|
||||||
|
self.assertRaises(OSError, platform_utils.remove, path, missing_ok=False)
|
||||||
|
|
||||||
|
# Should not fail if it exists.
|
||||||
|
open(path, 'w').close()
|
||||||
|
platform_utils.remove(path, missing_ok=True)
|
||||||
|
self.assertFalse(os.path.exists(path))
|
||||||
|
|
||||||
|
open(path, 'w').close()
|
||||||
|
platform_utils.remove(path)
|
||||||
|
self.assertFalse(os.path.exists(path))
|
||||||
|
|
||||||
|
open(path, 'w').close()
|
||||||
|
platform_utils.remove(path, missing_ok=False)
|
||||||
|
self.assertFalse(os.path.exists(path))
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -335,3 +336,76 @@ class LinkFile(CopyLinkTestCase):
|
|||||||
platform_utils.symlink(self.tempdir, dest)
|
platform_utils.symlink(self.tempdir, dest)
|
||||||
lf._Link()
|
lf._Link()
|
||||||
self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest))
|
self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest))
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateWorkTreeTests(unittest.TestCase):
|
||||||
|
"""Check _MigrateOldWorkTreeGitDir handling."""
|
||||||
|
|
||||||
|
_SYMLINKS = {
|
||||||
|
'config', 'description', 'hooks', 'info', 'logs', 'objects',
|
||||||
|
'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
|
||||||
|
}
|
||||||
|
_FILES = {
|
||||||
|
'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD',
|
||||||
|
'unknown-file-should-be-migrated',
|
||||||
|
}
|
||||||
|
_CLEAN_FILES = {
|
||||||
|
'a-vim-temp-file~', '#an-emacs-temp-file#',
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _simple_layout(cls):
|
||||||
|
"""Create a simple repo client checkout to test against."""
|
||||||
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
|
tempdir = Path(tempdir)
|
||||||
|
|
||||||
|
gitdir = tempdir / '.repo/projects/src/test.git'
|
||||||
|
gitdir.mkdir(parents=True)
|
||||||
|
cmd = ['git', 'init', '--bare', str(gitdir)]
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
dotgit = tempdir / 'src/test/.git'
|
||||||
|
dotgit.mkdir(parents=True)
|
||||||
|
for name in cls._SYMLINKS:
|
||||||
|
(dotgit / name).symlink_to(f'../../../.repo/projects/src/test.git/{name}')
|
||||||
|
for name in cls._FILES | cls._CLEAN_FILES:
|
||||||
|
(dotgit / name).write_text(name)
|
||||||
|
|
||||||
|
yield tempdir
|
||||||
|
|
||||||
|
def test_standard(self):
|
||||||
|
"""Migrate a standard checkout that we expect."""
|
||||||
|
with self._simple_layout() as tempdir:
|
||||||
|
dotgit = tempdir / 'src/test/.git'
|
||||||
|
project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
|
||||||
|
|
||||||
|
# Make sure the dir was transformed into a symlink.
|
||||||
|
self.assertTrue(dotgit.is_symlink())
|
||||||
|
self.assertEqual(str(dotgit.readlink()), '../../.repo/projects/src/test.git')
|
||||||
|
|
||||||
|
# Make sure files were moved over.
|
||||||
|
gitdir = tempdir / '.repo/projects/src/test.git'
|
||||||
|
for name in self._FILES:
|
||||||
|
self.assertEqual(name, (gitdir / name).read_text())
|
||||||
|
# Make sure files were removed.
|
||||||
|
for name in self._CLEAN_FILES:
|
||||||
|
self.assertFalse((gitdir / name).exists())
|
||||||
|
|
||||||
|
def test_unknown(self):
|
||||||
|
"""A checkout with unknown files should abort."""
|
||||||
|
with self._simple_layout() as tempdir:
|
||||||
|
dotgit = tempdir / 'src/test/.git'
|
||||||
|
(tempdir / '.repo/projects/src/test.git/random-file').write_text('one')
|
||||||
|
(dotgit / 'random-file').write_text('two')
|
||||||
|
with self.assertRaises(error.GitError):
|
||||||
|
project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
|
||||||
|
|
||||||
|
# Make sure no content was actually changed.
|
||||||
|
self.assertTrue(dotgit.is_dir())
|
||||||
|
for name in self._FILES:
|
||||||
|
self.assertTrue((dotgit / name).is_file())
|
||||||
|
for name in self._CLEAN_FILES:
|
||||||
|
self.assertTrue((dotgit / name).is_file())
|
||||||
|
for name in self._SYMLINKS:
|
||||||
|
self.assertTrue((dotgit / name).is_symlink())
|
||||||
|
Reference in New Issue
Block a user