mirror of
https://gerrit.googlesource.com/git-repo
synced 2025-06-30 20:17:08 +00:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
acc4c857a0 | |||
a39af3d432 | |||
4cdfdb7734 | |||
1eddca8476 | |||
aefa4d3a29 | |||
4ba29c42ca | |||
45ef9011c2 | |||
891e8f72ce | |||
af8fb132d5 | |||
4112c07688 | |||
fbd5dd3a30 | |||
3d27c71dd9 | |||
488d54d4ee | |||
5a5cfce1b2 | |||
e6d4b84060 | |||
d75ca2eb9d | |||
a010a9f4a0 | |||
8a54a7eac3 | |||
63a5657ecf | |||
07d21e6bde | |||
076d54652e | |||
790f4cea7a | |||
39cb17f7a3 | |||
ad1b7bd2e2 | |||
3c2d807905 | |||
7fa8eedd8f | |||
dede564c3d | |||
ac76fd3e3a | |||
a8c34d1075 | |||
5951e3043f | |||
48ea25c6a7 | |||
355f4398d8 | |||
bddc964d93 |
@ -105,6 +105,8 @@ following DTD:
|
|||||||
<!ATTLIST extend-project groups CDATA #IMPLIED>
|
<!ATTLIST extend-project groups CDATA #IMPLIED>
|
||||||
<!ATTLIST extend-project revision CDATA #IMPLIED>
|
<!ATTLIST extend-project revision CDATA #IMPLIED>
|
||||||
<!ATTLIST extend-project remote CDATA #IMPLIED>
|
<!ATTLIST extend-project remote CDATA #IMPLIED>
|
||||||
|
<!ATTLIST extend-project dest-branch CDATA #IMPLIED>
|
||||||
|
<!ATTLIST extend-project upstream CDATA #IMPLIED>
|
||||||
|
|
||||||
<!ELEMENT remove-project EMPTY>
|
<!ELEMENT remove-project EMPTY>
|
||||||
<!ATTLIST remove-project name CDATA #REQUIRED>
|
<!ATTLIST remove-project name CDATA #REQUIRED>
|
||||||
@ -423,6 +425,12 @@ project. Same syntax as the corresponding element of `project`.
|
|||||||
Attribute `remote`: If specified, overrides the remote of the original
|
Attribute `remote`: If specified, overrides the remote of the original
|
||||||
project. Same syntax as the corresponding element of `project`.
|
project. Same syntax as the corresponding element of `project`.
|
||||||
|
|
||||||
|
Attribute `dest-branch`: If specified, overrides the dest-branch of the original
|
||||||
|
project. Same syntax as the corresponding element of `project`.
|
||||||
|
|
||||||
|
Attribute `upstream`: If specified, overrides the upstream of the original
|
||||||
|
project. Same syntax as the corresponding element of `project`.
|
||||||
|
|
||||||
### Element annotation
|
### Element annotation
|
||||||
|
|
||||||
Zero or more annotation elements may be specified as children of a
|
Zero or more annotation elements may be specified as children of a
|
||||||
|
@ -158,6 +158,8 @@ def git_require(min_version, fail=False, msg=''):
|
|||||||
|
|
||||||
|
|
||||||
class GitCommand(object):
|
class GitCommand(object):
|
||||||
|
"""Wrapper around a single git invocation."""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
project,
|
project,
|
||||||
cmdv,
|
cmdv,
|
||||||
@ -279,14 +281,9 @@ class GitCommand(object):
|
|||||||
ssh_proxy.add_client(p)
|
ssh_proxy.add_client(p)
|
||||||
|
|
||||||
self.process = p
|
self.process = p
|
||||||
if input:
|
|
||||||
if isinstance(input, str):
|
|
||||||
input = input.encode('utf-8')
|
|
||||||
p.stdin.write(input)
|
|
||||||
p.stdin.close()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.stdout, self.stderr = p.communicate()
|
self.stdout, self.stderr = p.communicate(input=input)
|
||||||
finally:
|
finally:
|
||||||
if ssh_proxy:
|
if ssh_proxy:
|
||||||
ssh_proxy.remove_client(p)
|
ssh_proxy.remove_client(p)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# From Gerrit Code Review 3.1.3
|
# From Gerrit Code Review 3.6.1 c67916dbdc07555c44e32a68f92ffc484b9b34f0
|
||||||
#
|
#
|
||||||
# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
|
# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
|
||||||
#
|
#
|
||||||
@ -17,6 +17,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
# avoid [[ which is not POSIX sh.
|
# avoid [[ which is not POSIX sh.
|
||||||
if test "$#" != 1 ; then
|
if test "$#" != 1 ; then
|
||||||
echo "$0 requires an argument."
|
echo "$0 requires an argument."
|
||||||
@ -29,15 +31,25 @@ if test ! -f "$1" ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Do not create a change id if requested
|
# Do not create a change id if requested
|
||||||
if test "false" = "`git config --bool --get gerrit.createChangeId`" ; then
|
if test "false" = "$(git config --bool --get gerrit.createChangeId)" ; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# $RANDOM will be undefined if not using bash, so don't use set -u
|
# Do not create a change id for squash commits.
|
||||||
random=$( (whoami ; hostname ; date; cat $1 ; echo $RANDOM) | git hash-object --stdin)
|
if head -n1 "$1" | grep -q '^squash! '; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git rev-parse --verify HEAD >/dev/null 2>&1; then
|
||||||
|
refhash="$(git rev-parse HEAD)"
|
||||||
|
else
|
||||||
|
refhash="$(git hash-object -t tree /dev/null)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
random=$({ git var GIT_COMMITTER_IDENT ; echo "$refhash" ; cat "$1"; } | git hash-object --stdin)
|
||||||
dest="$1.tmp.${random}"
|
dest="$1.tmp.${random}"
|
||||||
|
|
||||||
trap 'rm -f "${dest}"' EXIT
|
trap 'rm -f "$dest" "$dest-2"' EXIT
|
||||||
|
|
||||||
if ! git stripspace --strip-comments < "$1" > "${dest}" ; then
|
if ! git stripspace --strip-comments < "$1" > "${dest}" ; then
|
||||||
echo "cannot strip comments from $1"
|
echo "cannot strip comments from $1"
|
||||||
@ -49,11 +61,40 @@ if test ! -s "${dest}" ; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
reviewurl="$(git config --get gerrit.reviewUrl)"
|
||||||
|
if test -n "${reviewurl}" ; then
|
||||||
|
token="Link"
|
||||||
|
value="${reviewurl%/}/id/I$random"
|
||||||
|
pattern=".*/id/I[0-9a-f]\{40\}$"
|
||||||
|
else
|
||||||
|
token="Change-Id"
|
||||||
|
value="I$random"
|
||||||
|
pattern=".*"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git interpret-trailers --parse < "$1" | grep -q "^$token: $pattern$" ; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# There must be a Signed-off-by trailer for the code below to work. Insert a
|
||||||
|
# sentinel at the end to make sure there is one.
|
||||||
# Avoid the --in-place option which only appeared in Git 2.8
|
# Avoid the --in-place option which only appeared in Git 2.8
|
||||||
# Avoid the --if-exists option which only appeared in Git 2.15
|
if ! git interpret-trailers \
|
||||||
if ! git -c trailer.ifexists=doNothing interpret-trailers \
|
--trailer "Signed-off-by: SENTINEL" < "$1" > "$dest-2" ; then
|
||||||
--trailer "Change-Id: I${random}" < "$1" > "${dest}" ; then
|
echo "cannot insert Signed-off-by sentinel line in $1"
|
||||||
echo "cannot insert change-id line in $1"
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure the trailer appears before any Signed-off-by trailers by inserting
|
||||||
|
# it as if it was a Signed-off-by trailer and then use sed to remove the
|
||||||
|
# Signed-off-by prefix and the Signed-off-by sentinel line.
|
||||||
|
# Avoid the --in-place option which only appeared in Git 2.8
|
||||||
|
# Avoid the --where option which only appeared in Git 2.15
|
||||||
|
if ! git -c trailer.where=before interpret-trailers \
|
||||||
|
--trailer "Signed-off-by: $token: $value" < "$dest-2" |
|
||||||
|
sed -re "s/^Signed-off-by: ($token: )/\1/" \
|
||||||
|
-e "/^Signed-off-by: SENTINEL/d" > "$dest" ; then
|
||||||
|
echo "cannot insert $token line in $1"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -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 2022" "repo gitc-init" "Repo Manual"
|
.TH REPO "1" "August 2022" "repo gitc-init" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo gitc-init - manual page for repo gitc-init
|
repo \- repo gitc-init - manual page for repo gitc-init
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -45,10 +45,15 @@ sync any submodules associated with the manifest repo
|
|||||||
\fB\-\-standalone\-manifest\fR
|
\fB\-\-standalone\-manifest\fR
|
||||||
download the manifest as a static file rather then
|
download the manifest as a static file rather then
|
||||||
create a git checkout of the manifest repo
|
create a git checkout of the manifest repo
|
||||||
|
.TP
|
||||||
|
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
|
||||||
|
create a shallow clone of the manifest repo with given
|
||||||
|
depth; see git clone (default: 1)
|
||||||
.SS Manifest (only) checkout options:
|
.SS Manifest (only) checkout options:
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-current\-branch\fR
|
\fB\-\-current\-branch\fR
|
||||||
fetch only current manifest branch from server
|
fetch only current manifest branch from server
|
||||||
|
(default)
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-current\-branch\fR
|
\fB\-\-no\-current\-branch\fR
|
||||||
fetch all manifest branches from server
|
fetch all manifest branches from server
|
||||||
|
@ -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 2022" "repo init" "Repo Manual"
|
.TH REPO "1" "August 2022" "repo init" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo init - manual page for repo init
|
repo \- repo init - manual page for repo init
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -45,10 +45,15 @@ sync any submodules associated with the manifest repo
|
|||||||
\fB\-\-standalone\-manifest\fR
|
\fB\-\-standalone\-manifest\fR
|
||||||
download the manifest as a static file rather then
|
download the manifest as a static file rather then
|
||||||
create a git checkout of the manifest repo
|
create a git checkout of the manifest repo
|
||||||
|
.TP
|
||||||
|
\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
|
||||||
|
create a shallow clone of the manifest repo with given
|
||||||
|
depth; see git clone (default: 1)
|
||||||
.SS Manifest (only) checkout options:
|
.SS Manifest (only) checkout options:
|
||||||
.TP
|
.TP
|
||||||
\fB\-c\fR, \fB\-\-current\-branch\fR
|
\fB\-c\fR, \fB\-\-current\-branch\fR
|
||||||
fetch only current manifest branch from server
|
fetch only current manifest branch from server
|
||||||
|
(default)
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-current\-branch\fR
|
\fB\-\-no\-current\-branch\fR
|
||||||
fetch all manifest branches from server
|
fetch all manifest branches from server
|
||||||
|
@ -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 2022" "repo smartsync" "Repo Manual"
|
.TH REPO "1" "August 2022" "repo smartsync" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo smartsync - manual page for repo smartsync
|
repo \- repo smartsync - manual page for repo smartsync
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -20,11 +20,11 @@ number of CPU cores)
|
|||||||
.TP
|
.TP
|
||||||
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
|
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
|
||||||
number of network jobs to run in parallel (defaults to
|
number of network jobs to run in parallel (defaults to
|
||||||
\fB\-\-jobs\fR)
|
\fB\-\-jobs\fR or 1)
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
|
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
|
||||||
number of local checkout jobs to run in parallel
|
number of local checkout jobs to run in parallel
|
||||||
(defaults to \fB\-\-jobs\fR)
|
(defaults to \fB\-\-jobs\fR or 8)
|
||||||
.TP
|
.TP
|
||||||
\fB\-f\fR, \fB\-\-force\-broken\fR
|
\fB\-f\fR, \fB\-\-force\-broken\fR
|
||||||
obsolete option (to be deleted in the future)
|
obsolete option (to be deleted in the future)
|
||||||
|
@ -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 2022" "repo sync" "Repo Manual"
|
.TH REPO "1" "August 2022" "repo sync" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo sync - manual page for repo sync
|
repo \- repo sync - manual page for repo sync
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -20,11 +20,11 @@ number of CPU cores)
|
|||||||
.TP
|
.TP
|
||||||
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
|
\fB\-\-jobs\-network\fR=\fI\,JOBS\/\fR
|
||||||
number of network jobs to run in parallel (defaults to
|
number of network jobs to run in parallel (defaults to
|
||||||
\fB\-\-jobs\fR)
|
\fB\-\-jobs\fR or 1)
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
|
\fB\-\-jobs\-checkout\fR=\fI\,JOBS\/\fR
|
||||||
number of local checkout jobs to run in parallel
|
number of local checkout jobs to run in parallel
|
||||||
(defaults to \fB\-\-jobs\fR)
|
(defaults to \fB\-\-jobs\fR or 8)
|
||||||
.TP
|
.TP
|
||||||
\fB\-f\fR, \fB\-\-force\-broken\fR
|
\fB\-f\fR, \fB\-\-force\-broken\fR
|
||||||
obsolete option (to be deleted in the future)
|
obsolete option (to be deleted in the future)
|
||||||
|
@ -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 2022" "repo upload" "Repo Manual"
|
.TH REPO "1" "August 2022" "repo upload" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo upload - manual page for repo upload
|
repo \- repo upload - manual page for repo upload
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -54,6 +54,9 @@ upload as a private change (deprecated; use \fB\-\-wip\fR)
|
|||||||
\fB\-w\fR, \fB\-\-wip\fR
|
\fB\-w\fR, \fB\-\-wip\fR
|
||||||
upload as a work\-in\-progress change
|
upload as a work\-in\-progress change
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-r\fR, \fB\-\-ready\fR
|
||||||
|
mark change as ready (clears work\-in\-progress setting)
|
||||||
|
.TP
|
||||||
\fB\-o\fR PUSH_OPTIONS, \fB\-\-push\-option\fR=\fI\,PUSH_OPTIONS\/\fR
|
\fB\-o\fR PUSH_OPTIONS, \fB\-\-push\-option\fR=\fI\,PUSH_OPTIONS\/\fR
|
||||||
additional push options to transmit
|
additional push options to transmit
|
||||||
.TP
|
.TP
|
||||||
@ -66,6 +69,12 @@ do everything except actually upload the CL
|
|||||||
\fB\-y\fR, \fB\-\-yes\fR
|
\fB\-y\fR, \fB\-\-yes\fR
|
||||||
answer yes to all safe prompts
|
answer yes to all safe prompts
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-\-ignore\-untracked\-files\fR
|
||||||
|
ignore untracked files in the working copy
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-ignore\-untracked\-files\fR
|
||||||
|
always ask about untracked files in the working copy
|
||||||
|
.TP
|
||||||
\fB\-\-no\-cert\-checks\fR
|
\fB\-\-no\-cert\-checks\fR
|
||||||
disable verifying ssl certs (unsafe)
|
disable verifying ssl certs (unsafe)
|
||||||
.SS Logging options:
|
.SS Logging options:
|
||||||
@ -118,6 +127,12 @@ respective list of users, and emails are sent to any new users. Users passed as
|
|||||||
\fB\-\-reviewers\fR must already be registered with the code review system, or the
|
\fB\-\-reviewers\fR must already be registered with the code review system, or the
|
||||||
upload will fail.
|
upload will fail.
|
||||||
.PP
|
.PP
|
||||||
|
While most normal Gerrit options have dedicated command line options, direct
|
||||||
|
access to the Gerit options is available via \fB\-\-push\-options\fR. This is useful when
|
||||||
|
Gerrit has newer functionality that repo upload doesn't yet support, or doesn't
|
||||||
|
have plans to support. See the Push Options documentation for more details:
|
||||||
|
https://gerrit\-review.googlesource.com/Documentation/user\-upload.html#push_options
|
||||||
|
.PP
|
||||||
Configuration
|
Configuration
|
||||||
.PP
|
.PP
|
||||||
review.URL.autoupload:
|
review.URL.autoupload:
|
||||||
|
@ -123,7 +123,7 @@ class _Default(object):
|
|||||||
destBranchExpr = None
|
destBranchExpr = None
|
||||||
upstreamExpr = None
|
upstreamExpr = None
|
||||||
remote = None
|
remote = None
|
||||||
sync_j = 1
|
sync_j = None
|
||||||
sync_c = False
|
sync_c = False
|
||||||
sync_s = False
|
sync_s = False
|
||||||
sync_tags = True
|
sync_tags = True
|
||||||
@ -284,7 +284,7 @@ class _XmlSubmanifest:
|
|||||||
if self.project:
|
if self.project:
|
||||||
manifestUrl = remote.ToRemoteSpec(self.project).url
|
manifestUrl = remote.ToRemoteSpec(self.project).url
|
||||||
else:
|
else:
|
||||||
manifestUrl = mp.GetRemote(mp.remote.name).url
|
manifestUrl = mp.GetRemote().url
|
||||||
manifestName = self.manifestName or 'default.xml'
|
manifestName = self.manifestName or 'default.xml'
|
||||||
revision = self.revision or self.name
|
revision = self.revision or self.name
|
||||||
path = self.path or revision.split('/')[-1]
|
path = self.path or revision.split('/')[-1]
|
||||||
@ -358,7 +358,10 @@ class XmlManifest(object):
|
|||||||
|
|
||||||
self.repodir = os.path.abspath(repodir)
|
self.repodir = os.path.abspath(repodir)
|
||||||
self._CheckLocalPath(submanifest_path)
|
self._CheckLocalPath(submanifest_path)
|
||||||
self.topdir = os.path.join(os.path.dirname(self.repodir), submanifest_path)
|
self.topdir = os.path.dirname(self.repodir)
|
||||||
|
if submanifest_path:
|
||||||
|
# This avoids a trailing os.path.sep when submanifest_path is empty.
|
||||||
|
self.topdir = os.path.join(self.topdir, submanifest_path)
|
||||||
if manifest_file != os.path.abspath(manifest_file):
|
if manifest_file != os.path.abspath(manifest_file):
|
||||||
raise ManifestParseError('manifest_file must be abspath')
|
raise ManifestParseError('manifest_file must be abspath')
|
||||||
self.manifestFile = manifest_file
|
self.manifestFile = manifest_file
|
||||||
@ -548,7 +551,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
if d.upstreamExpr:
|
if d.upstreamExpr:
|
||||||
have_default = True
|
have_default = True
|
||||||
e.setAttribute('upstream', d.upstreamExpr)
|
e.setAttribute('upstream', d.upstreamExpr)
|
||||||
if d.sync_j > 1:
|
if d.sync_j is not None:
|
||||||
have_default = True
|
have_default = True
|
||||||
e.setAttribute('sync-j', '%d' % d.sync_j)
|
e.setAttribute('sync-j', '%d' % d.sync_j)
|
||||||
if d.sync_c:
|
if d.sync_c:
|
||||||
@ -1286,6 +1289,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
remote = self._default.remote
|
remote = self._default.remote
|
||||||
else:
|
else:
|
||||||
remote = self._get_remote(node)
|
remote = self._get_remote(node)
|
||||||
|
dest_branch = node.getAttribute('dest-branch')
|
||||||
|
upstream = node.getAttribute('upstream')
|
||||||
|
|
||||||
named_projects = self._projects[name]
|
named_projects = self._projects[name]
|
||||||
if dest_path and not path and len(named_projects) > 1:
|
if dest_path and not path and len(named_projects) > 1:
|
||||||
@ -1301,6 +1306,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
|
|
||||||
if remote_name:
|
if remote_name:
|
||||||
p.remote = remote.ToRemoteSpec(name)
|
p.remote = remote.ToRemoteSpec(name)
|
||||||
|
if dest_branch:
|
||||||
|
p.dest_branch = dest_branch
|
||||||
|
if upstream:
|
||||||
|
p.upstream = upstream
|
||||||
|
|
||||||
if dest_path:
|
if dest_path:
|
||||||
del self._paths[p.relpath]
|
del self._paths[p.relpath]
|
||||||
@ -1385,7 +1394,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
|
|
||||||
def _AddMetaProjectMirror(self, m):
|
def _AddMetaProjectMirror(self, m):
|
||||||
name = None
|
name = None
|
||||||
m_url = m.GetRemote(m.remote.name).url
|
m_url = m.GetRemote().url
|
||||||
if m_url.endswith('/.git'):
|
if m_url.endswith('/.git'):
|
||||||
raise ManifestParseError('refusing to mirror %s' % m_url)
|
raise ManifestParseError('refusing to mirror %s' % m_url)
|
||||||
|
|
||||||
@ -1462,8 +1471,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
d.destBranchExpr = node.getAttribute('dest-branch') or None
|
d.destBranchExpr = node.getAttribute('dest-branch') or None
|
||||||
d.upstreamExpr = node.getAttribute('upstream') or None
|
d.upstreamExpr = node.getAttribute('upstream') or None
|
||||||
|
|
||||||
d.sync_j = XmlInt(node, 'sync-j', 1)
|
d.sync_j = XmlInt(node, 'sync-j', None)
|
||||||
if d.sync_j <= 0:
|
if d.sync_j is not None and d.sync_j <= 0:
|
||||||
raise ManifestParseError('%s: sync-j must be greater than 0, not "%s"' %
|
raise ManifestParseError('%s: sync-j must be greater than 0, not "%s"' %
|
||||||
(self.manifestFile, d.sync_j))
|
(self.manifestFile, d.sync_j))
|
||||||
|
|
||||||
@ -1937,11 +1946,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
fromKeys = sorted(fromProjects.keys())
|
fromKeys = sorted(fromProjects.keys())
|
||||||
toKeys = sorted(toProjects.keys())
|
toKeys = sorted(toProjects.keys())
|
||||||
|
|
||||||
diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}
|
diff = {'added': [], 'removed': [], 'missing': [], 'changed': [], 'unreachable': []}
|
||||||
|
|
||||||
for proj in fromKeys:
|
for proj in fromKeys:
|
||||||
if proj not in toKeys:
|
if proj not in toKeys:
|
||||||
diff['removed'].append(fromProjects[proj])
|
diff['removed'].append(fromProjects[proj])
|
||||||
|
elif not fromProjects[proj].Exists:
|
||||||
|
diff['missing'].append(toProjects[proj])
|
||||||
|
toKeys.remove(proj)
|
||||||
else:
|
else:
|
||||||
fromProj = fromProjects[proj]
|
fromProj = fromProjects[proj]
|
||||||
toProj = toProjects[proj]
|
toProj = toProjects[proj]
|
||||||
|
7
pager.py
7
pager.py
@ -56,8 +56,11 @@ def _PipePager(pager):
|
|||||||
global pager_process, old_stdout, old_stderr
|
global pager_process, old_stdout, old_stderr
|
||||||
assert pager_process is None, "Only one active pager process at a time"
|
assert pager_process is None, "Only one active pager process at a time"
|
||||||
# Create pager process, piping stdout/err into its stdin
|
# Create pager process, piping stdout/err into its stdin
|
||||||
pager_process = subprocess.Popen([pager], stdin=subprocess.PIPE, stdout=sys.stdout,
|
try:
|
||||||
stderr=sys.stderr)
|
pager_process = subprocess.Popen([pager], stdin=subprocess.PIPE, stdout=sys.stdout,
|
||||||
|
stderr=sys.stderr)
|
||||||
|
except FileNotFoundError:
|
||||||
|
sys.exit(f'fatal: cannot start pager "{pager}"')
|
||||||
old_stdout = sys.stdout
|
old_stdout = sys.stdout
|
||||||
old_stderr = sys.stderr
|
old_stderr = sys.stderr
|
||||||
sys.stdout = pager_process.stdin
|
sys.stdout = pager_process.stdin
|
||||||
|
81
project.py
81
project.py
@ -26,6 +26,7 @@ import sys
|
|||||||
import tarfile
|
import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
from typing import NamedTuple
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from color import Coloring
|
from color import Coloring
|
||||||
@ -45,6 +46,14 @@ from repo_trace import IsTrace, Trace
|
|||||||
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
|
from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
|
||||||
|
|
||||||
|
|
||||||
|
class SyncNetworkHalfResult(NamedTuple):
|
||||||
|
"""Sync_NetworkHalf return value."""
|
||||||
|
# True if successful.
|
||||||
|
success: bool
|
||||||
|
# Did we query the remote? False when optimized_fetch is True and we have the
|
||||||
|
# commit already present.
|
||||||
|
remote_fetched: bool
|
||||||
|
|
||||||
# Maximum sleep time allowed during retries.
|
# Maximum sleep time allowed during retries.
|
||||||
MAXIMUM_RETRY_SLEEP_SEC = 3600.0
|
MAXIMUM_RETRY_SLEEP_SEC = 3600.0
|
||||||
# +-10% random jitter is added to each Fetches retry sleep duration.
|
# +-10% random jitter is added to each Fetches retry sleep duration.
|
||||||
@ -205,6 +214,7 @@ class ReviewableBranch(object):
|
|||||||
private=False,
|
private=False,
|
||||||
notify=None,
|
notify=None,
|
||||||
wip=False,
|
wip=False,
|
||||||
|
ready=False,
|
||||||
dest_branch=None,
|
dest_branch=None,
|
||||||
validate_certs=True,
|
validate_certs=True,
|
||||||
push_options=None):
|
push_options=None):
|
||||||
@ -217,6 +227,7 @@ class ReviewableBranch(object):
|
|||||||
private=private,
|
private=private,
|
||||||
notify=notify,
|
notify=notify,
|
||||||
wip=wip,
|
wip=wip,
|
||||||
|
ready=ready,
|
||||||
dest_branch=dest_branch,
|
dest_branch=dest_branch,
|
||||||
validate_certs=validate_certs,
|
validate_certs=validate_certs,
|
||||||
push_options=push_options)
|
push_options=push_options)
|
||||||
@ -684,9 +695,13 @@ class Project(object):
|
|||||||
self._userident_name = ''
|
self._userident_name = ''
|
||||||
self._userident_email = ''
|
self._userident_email = ''
|
||||||
|
|
||||||
def GetRemote(self, name):
|
def GetRemote(self, name=None):
|
||||||
"""Get the configuration for a single remote.
|
"""Get the configuration for a single remote.
|
||||||
|
|
||||||
|
Defaults to the current project's remote.
|
||||||
"""
|
"""
|
||||||
|
if name is None:
|
||||||
|
name = self.remote.name
|
||||||
return self.config.GetRemote(name)
|
return self.config.GetRemote(name)
|
||||||
|
|
||||||
def GetBranch(self, name):
|
def GetBranch(self, name):
|
||||||
@ -1003,6 +1018,7 @@ class Project(object):
|
|||||||
private=False,
|
private=False,
|
||||||
notify=None,
|
notify=None,
|
||||||
wip=False,
|
wip=False,
|
||||||
|
ready=False,
|
||||||
dest_branch=None,
|
dest_branch=None,
|
||||||
validate_certs=True,
|
validate_certs=True,
|
||||||
push_options=None):
|
push_options=None):
|
||||||
@ -1072,6 +1088,8 @@ class Project(object):
|
|||||||
opts += ['private']
|
opts += ['private']
|
||||||
if wip:
|
if wip:
|
||||||
opts += ['wip']
|
opts += ['wip']
|
||||||
|
if ready:
|
||||||
|
opts += ['ready']
|
||||||
if opts:
|
if opts:
|
||||||
ref_spec = ref_spec + '%' + ','.join(opts)
|
ref_spec = ref_spec + '%' + ','.join(opts)
|
||||||
cmd.append(ref_spec)
|
cmd.append(ref_spec)
|
||||||
@ -1124,7 +1142,7 @@ class Project(object):
|
|||||||
if archive and not isinstance(self, MetaProject):
|
if archive and not isinstance(self, MetaProject):
|
||||||
if self.remote.url.startswith(('http://', 'https://')):
|
if self.remote.url.startswith(('http://', 'https://')):
|
||||||
_error("%s: Cannot fetch archives from http/https remotes.", self.name)
|
_error("%s: Cannot fetch archives from http/https remotes.", self.name)
|
||||||
return False
|
return SyncNetworkHalfResult(False, False)
|
||||||
|
|
||||||
name = self.relpath.replace('\\', '/')
|
name = self.relpath.replace('\\', '/')
|
||||||
name = name.replace('/', '_')
|
name = name.replace('/', '_')
|
||||||
@ -1135,19 +1153,19 @@ class Project(object):
|
|||||||
self._FetchArchive(tarpath, cwd=topdir)
|
self._FetchArchive(tarpath, cwd=topdir)
|
||||||
except GitError as e:
|
except GitError as e:
|
||||||
_error('%s', e)
|
_error('%s', e)
|
||||||
return False
|
return SyncNetworkHalfResult(False, False)
|
||||||
|
|
||||||
# From now on, we only need absolute tarpath
|
# From now on, we only need absolute tarpath
|
||||||
tarpath = os.path.join(topdir, tarpath)
|
tarpath = os.path.join(topdir, tarpath)
|
||||||
|
|
||||||
if not self._ExtractArchive(tarpath, path=topdir):
|
if not self._ExtractArchive(tarpath, path=topdir):
|
||||||
return False
|
return SyncNetworkHalfResult(False, True)
|
||||||
try:
|
try:
|
||||||
platform_utils.remove(tarpath)
|
platform_utils.remove(tarpath)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
_warn("Cannot remove archive %s: %s", tarpath, str(e))
|
_warn("Cannot remove archive %s: %s", tarpath, str(e))
|
||||||
self._CopyAndLinkFiles()
|
self._CopyAndLinkFiles()
|
||||||
return True
|
return SyncNetworkHalfResult(True, True)
|
||||||
|
|
||||||
# If the shared object dir already exists, don't try to rebootstrap with a
|
# If the shared object dir already exists, don't try to rebootstrap with a
|
||||||
# clone bundle download. We should have the majority of objects already.
|
# clone bundle download. We should have the majority of objects already.
|
||||||
@ -1211,9 +1229,11 @@ class Project(object):
|
|||||||
depth = self.manifest.manifestProject.depth
|
depth = self.manifest.manifestProject.depth
|
||||||
|
|
||||||
# See if we can skip the network fetch entirely.
|
# See if we can skip the network fetch entirely.
|
||||||
|
remote_fetched = False
|
||||||
if not (optimized_fetch and
|
if not (optimized_fetch and
|
||||||
(ID_RE.match(self.revisionExpr) and
|
(ID_RE.match(self.revisionExpr) and
|
||||||
self._CheckForImmutableRevision())):
|
self._CheckForImmutableRevision())):
|
||||||
|
remote_fetched = True
|
||||||
if not self._RemoteFetch(
|
if not self._RemoteFetch(
|
||||||
initial=is_new,
|
initial=is_new,
|
||||||
quiet=quiet, verbose=verbose, output_redir=output_redir,
|
quiet=quiet, verbose=verbose, output_redir=output_redir,
|
||||||
@ -1222,7 +1242,7 @@ class Project(object):
|
|||||||
submodules=submodules, force_sync=force_sync,
|
submodules=submodules, force_sync=force_sync,
|
||||||
ssh_proxy=ssh_proxy,
|
ssh_proxy=ssh_proxy,
|
||||||
clone_filter=clone_filter, retry_fetches=retry_fetches):
|
clone_filter=clone_filter, retry_fetches=retry_fetches):
|
||||||
return False
|
return SyncNetworkHalfResult(False, remote_fetched)
|
||||||
|
|
||||||
mp = self.manifest.manifestProject
|
mp = self.manifest.manifestProject
|
||||||
dissociate = mp.dissociate
|
dissociate = mp.dissociate
|
||||||
@ -1235,7 +1255,7 @@ class Project(object):
|
|||||||
if p.stdout and output_redir:
|
if p.stdout and output_redir:
|
||||||
output_redir.write(p.stdout)
|
output_redir.write(p.stdout)
|
||||||
if p.Wait() != 0:
|
if p.Wait() != 0:
|
||||||
return False
|
return SyncNetworkHalfResult(False, remote_fetched)
|
||||||
platform_utils.remove(alternates_file)
|
platform_utils.remove(alternates_file)
|
||||||
|
|
||||||
if self.worktree:
|
if self.worktree:
|
||||||
@ -1244,7 +1264,7 @@ class Project(object):
|
|||||||
self._InitMirrorHead()
|
self._InitMirrorHead()
|
||||||
platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
|
platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'),
|
||||||
missing_ok=True)
|
missing_ok=True)
|
||||||
return True
|
return SyncNetworkHalfResult(True, remote_fetched)
|
||||||
|
|
||||||
def PostRepoUpgrade(self):
|
def PostRepoUpgrade(self):
|
||||||
self._InitHooks()
|
self._InitHooks()
|
||||||
@ -1277,7 +1297,7 @@ class Project(object):
|
|||||||
if self.revisionId:
|
if self.revisionId:
|
||||||
return self.revisionId
|
return self.revisionId
|
||||||
|
|
||||||
rem = self.GetRemote(self.remote.name)
|
rem = self.GetRemote()
|
||||||
rev = rem.ToLocal(self.revisionExpr)
|
rev = rem.ToLocal(self.revisionExpr)
|
||||||
|
|
||||||
if all_refs is not None and rev in all_refs:
|
if all_refs is not None and rev in all_refs:
|
||||||
@ -1442,6 +1462,8 @@ class Project(object):
|
|||||||
cnt_mine += 1
|
cnt_mine += 1
|
||||||
|
|
||||||
if not upstream_gain and cnt_mine == len(local_changes):
|
if not upstream_gain and cnt_mine == len(local_changes):
|
||||||
|
# The copy/linkfile config may have changed.
|
||||||
|
self._CopyAndLinkFiles()
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.IsDirty(consider_untracked=False):
|
if self.IsDirty(consider_untracked=False):
|
||||||
@ -1469,7 +1491,7 @@ class Project(object):
|
|||||||
"discarding %d commits removed from upstream",
|
"discarding %d commits removed from upstream",
|
||||||
len(local_changes) - cnt_mine)
|
len(local_changes) - cnt_mine)
|
||||||
|
|
||||||
branch.remote = self.GetRemote(self.remote.name)
|
branch.remote = self.GetRemote()
|
||||||
if not ID_RE.match(self.revisionExpr):
|
if not ID_RE.match(self.revisionExpr):
|
||||||
# in case of manifest sync the revisionExpr might be a SHA1
|
# in case of manifest sync the revisionExpr might be a SHA1
|
||||||
branch.merge = self.revisionExpr
|
branch.merge = self.revisionExpr
|
||||||
@ -1527,7 +1549,7 @@ class Project(object):
|
|||||||
def DownloadPatchSet(self, change_id, patch_id):
|
def DownloadPatchSet(self, change_id, patch_id):
|
||||||
"""Download a single patch set of a single change to FETCH_HEAD.
|
"""Download a single patch set of a single change to FETCH_HEAD.
|
||||||
"""
|
"""
|
||||||
remote = self.GetRemote(self.remote.name)
|
remote = self.GetRemote()
|
||||||
|
|
||||||
cmd = ['fetch', remote.name]
|
cmd = ['fetch', remote.name]
|
||||||
cmd.append('refs/changes/%2.2d/%d/%d'
|
cmd.append('refs/changes/%2.2d/%d/%d'
|
||||||
@ -1669,13 +1691,10 @@ class Project(object):
|
|||||||
|
|
||||||
all_refs = self.bare_ref.all
|
all_refs = self.bare_ref.all
|
||||||
if R_HEADS + name in all_refs:
|
if R_HEADS + name in all_refs:
|
||||||
return GitCommand(self,
|
return GitCommand(self, ['checkout', '-q', name, '--']).Wait() == 0
|
||||||
['checkout', name, '--'],
|
|
||||||
capture_stdout=True,
|
|
||||||
capture_stderr=True).Wait() == 0
|
|
||||||
|
|
||||||
branch = self.GetBranch(name)
|
branch = self.GetBranch(name)
|
||||||
branch.remote = self.GetRemote(self.remote.name)
|
branch.remote = self.GetRemote()
|
||||||
branch.merge = branch_merge
|
branch.merge = branch_merge
|
||||||
if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
|
if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
|
||||||
branch.merge = R_HEADS + branch_merge
|
branch.merge = R_HEADS + branch_merge
|
||||||
@ -1697,10 +1716,7 @@ class Project(object):
|
|||||||
branch.Save()
|
branch.Save()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if GitCommand(self,
|
if GitCommand(self, ['checkout', '-q', '-b', branch.name, revid]).Wait() == 0:
|
||||||
['checkout', '-b', branch.name, revid],
|
|
||||||
capture_stdout=True,
|
|
||||||
capture_stderr=True).Wait() == 0:
|
|
||||||
branch.Save()
|
branch.Save()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -2043,7 +2059,7 @@ class Project(object):
|
|||||||
self.bare_git.rev_list('-1', '--missing=allow-any',
|
self.bare_git.rev_list('-1', '--missing=allow-any',
|
||||||
'%s^0' % self.revisionExpr, '--')
|
'%s^0' % self.revisionExpr, '--')
|
||||||
if self.upstream:
|
if self.upstream:
|
||||||
rev = self.GetRemote(self.remote.name).ToLocal(self.upstream)
|
rev = self.GetRemote().ToLocal(self.upstream)
|
||||||
self.bare_git.rev_list('-1', '--missing=allow-any',
|
self.bare_git.rev_list('-1', '--missing=allow-any',
|
||||||
'%s^0' % rev, '--')
|
'%s^0' % rev, '--')
|
||||||
self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
|
self.bare_git.merge_base('--is-ancestor', self.revisionExpr, rev)
|
||||||
@ -2334,7 +2350,7 @@ class Project(object):
|
|||||||
if initial and (self.manifest.manifestProject.depth or self.clone_depth):
|
if initial and (self.manifest.manifestProject.depth or self.clone_depth):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
remote = self.GetRemote(self.remote.name)
|
remote = self.GetRemote()
|
||||||
bundle_url = remote.url + '/clone.bundle'
|
bundle_url = remote.url + '/clone.bundle'
|
||||||
bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
|
bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
|
||||||
if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
|
if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
|
||||||
@ -2655,7 +2671,7 @@ class Project(object):
|
|||||||
|
|
||||||
def _InitRemote(self):
|
def _InitRemote(self):
|
||||||
if self.remote.url:
|
if self.remote.url:
|
||||||
remote = self.GetRemote(self.remote.name)
|
remote = self.GetRemote()
|
||||||
remote.url = self.remote.url
|
remote.url = self.remote.url
|
||||||
remote.pushUrl = self.remote.pushUrl
|
remote.pushUrl = self.remote.pushUrl
|
||||||
remote.review = self.remote.review
|
remote.review = self.remote.review
|
||||||
@ -2668,6 +2684,7 @@ class Project(object):
|
|||||||
remote.Save()
|
remote.Save()
|
||||||
|
|
||||||
def _InitMRef(self):
|
def _InitMRef(self):
|
||||||
|
"""Initialize the pseudo m/<manifest branch> ref."""
|
||||||
if self.manifest.branch:
|
if self.manifest.branch:
|
||||||
if self.use_git_worktrees:
|
if self.use_git_worktrees:
|
||||||
# Set up the m/ space to point to the worktree-specific ref space.
|
# Set up the m/ space to point to the worktree-specific ref space.
|
||||||
@ -2697,6 +2714,16 @@ class Project(object):
|
|||||||
self._InitAnyMRef(HEAD, self.bare_git)
|
self._InitAnyMRef(HEAD, self.bare_git)
|
||||||
|
|
||||||
def _InitAnyMRef(self, ref, active_git, detach=False):
|
def _InitAnyMRef(self, ref, active_git, detach=False):
|
||||||
|
"""Initialize |ref| in |active_git| to the value in the manifest.
|
||||||
|
|
||||||
|
This points |ref| to the <project> setting in the manifest.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ref: The branch to update.
|
||||||
|
active_git: The git repository to make updates in.
|
||||||
|
detach: Whether to update target of symbolic refs, or overwrite the ref
|
||||||
|
directly (and thus make it non-symbolic).
|
||||||
|
"""
|
||||||
cur = self.bare_ref.symref(ref)
|
cur = self.bare_ref.symref(ref)
|
||||||
|
|
||||||
if self.revisionId:
|
if self.revisionId:
|
||||||
@ -2705,7 +2732,7 @@ class Project(object):
|
|||||||
dst = self.revisionId + '^0'
|
dst = self.revisionId + '^0'
|
||||||
active_git.UpdateRef(ref, dst, message=msg, detach=True)
|
active_git.UpdateRef(ref, dst, message=msg, detach=True)
|
||||||
else:
|
else:
|
||||||
remote = self.GetRemote(self.remote.name)
|
remote = self.GetRemote()
|
||||||
dst = remote.ToLocal(self.revisionExpr)
|
dst = remote.ToLocal(self.revisionExpr)
|
||||||
if cur != dst:
|
if cur != dst:
|
||||||
msg = 'manifest set to %s' % self.revisionExpr
|
msg = 'manifest set to %s' % self.revisionExpr
|
||||||
@ -3697,7 +3724,7 @@ class ManifestProject(MetaProject):
|
|||||||
|
|
||||||
# Set the remote URL before the remote branch as we might need it below.
|
# Set the remote URL before the remote branch as we might need it below.
|
||||||
if manifest_url:
|
if manifest_url:
|
||||||
r = self.GetRemote(self.remote.name)
|
r = self.GetRemote()
|
||||||
r.url = manifest_url
|
r.url = manifest_url
|
||||||
r.ResetFetch()
|
r.ResetFetch()
|
||||||
r.Save()
|
r.Save()
|
||||||
@ -3822,8 +3849,8 @@ class ManifestProject(MetaProject):
|
|||||||
is_new=is_new, quiet=not verbose, verbose=verbose,
|
is_new=is_new, quiet=not verbose, verbose=verbose,
|
||||||
clone_bundle=clone_bundle, current_branch_only=current_branch_only,
|
clone_bundle=clone_bundle, current_branch_only=current_branch_only,
|
||||||
tags=tags, submodules=submodules, clone_filter=clone_filter,
|
tags=tags, submodules=submodules, clone_filter=clone_filter,
|
||||||
partial_clone_exclude=self.manifest.PartialCloneExclude):
|
partial_clone_exclude=self.manifest.PartialCloneExclude).success:
|
||||||
r = self.GetRemote(self.remote.name)
|
r = self.GetRemote()
|
||||||
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
|
print('fatal: cannot obtain manifest %s' % r.url, file=sys.stderr)
|
||||||
|
|
||||||
# Better delete the manifest git dir if we created it; otherwise next
|
# Better delete the manifest git dir if we created it; otherwise next
|
||||||
|
@ -59,18 +59,26 @@ def main(argv):
|
|||||||
version = RepoSourceVersion()
|
version = RepoSourceVersion()
|
||||||
cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}',
|
cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}',
|
||||||
'-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}',
|
'-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}',
|
||||||
'-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), TOPDIR.joinpath('repo'),
|
'-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), './repo',
|
||||||
'-h', f'help {cmd}'] for cmd in subcmds.all_commands]
|
'-h', f'help {cmd}'] for cmd in subcmds.all_commands]
|
||||||
cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git',
|
cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git',
|
||||||
'-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}',
|
'-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}',
|
||||||
'-o', MANDIR.joinpath('repo.1.tmp'), TOPDIR.joinpath('repo'),
|
'-o', MANDIR.joinpath('repo.1.tmp'), './repo',
|
||||||
'-h', '--help-all'])
|
'-h', '--help-all'])
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tempdir:
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
repo_dir = Path(tempdir) / '.repo'
|
tempdir = Path(tempdir)
|
||||||
|
repo_dir = tempdir / '.repo'
|
||||||
repo_dir.mkdir()
|
repo_dir.mkdir()
|
||||||
(repo_dir / 'repo').symlink_to(TOPDIR)
|
(repo_dir / 'repo').symlink_to(TOPDIR)
|
||||||
|
|
||||||
|
# Create a repo wrapper using the active Python executable. We can't pass
|
||||||
|
# this directly to help2man as it's too simple, so insert it via shebang.
|
||||||
|
data = (TOPDIR / 'repo').read_text(encoding='utf-8')
|
||||||
|
tempbin = tempdir / 'repo'
|
||||||
|
tempbin.write_text(f'#!{sys.executable}\n' + data, encoding='utf-8')
|
||||||
|
tempbin.chmod(0o755)
|
||||||
|
|
||||||
# Run all cmd in parallel, and wait for them to finish.
|
# Run all cmd in parallel, and wait for them to finish.
|
||||||
with multiprocessing.Pool() as pool:
|
with multiprocessing.Pool() as pool:
|
||||||
pool.map(partial(worker, cwd=tempdir, check=True), cmdlist)
|
pool.map(partial(worker, cwd=tempdir, check=True), cmdlist)
|
||||||
|
21
repo
21
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, 21)
|
VERSION = (2, 29)
|
||||||
|
|
||||||
# increment this if the MAINTAINER_KEYS block is modified
|
# increment this if the MAINTAINER_KEYS block is modified
|
||||||
KEYRING_VERSION = (2, 3)
|
KEYRING_VERSION = (2, 3)
|
||||||
@ -316,6 +316,9 @@ def InitParser(parser, gitc_init=False):
|
|||||||
help='download the manifest as a static file '
|
help='download the manifest as a static file '
|
||||||
'rather then create a git checkout of '
|
'rather then create a git checkout of '
|
||||||
'the manifest repo')
|
'the manifest repo')
|
||||||
|
group.add_option('--manifest-depth', type='int', default=1, metavar='DEPTH',
|
||||||
|
help='create a shallow clone of the manifest repo with '
|
||||||
|
'given depth; see git clone (default: %default)')
|
||||||
|
|
||||||
# Options that only affect manifest project, and not any of the projects
|
# Options that only affect manifest project, and not any of the projects
|
||||||
# specified in the manifest itself.
|
# specified in the manifest itself.
|
||||||
@ -325,9 +328,9 @@ def InitParser(parser, gitc_init=False):
|
|||||||
# want -c, so try to satisfy both as best we can.
|
# want -c, so try to satisfy both as best we can.
|
||||||
if not gitc_init:
|
if not gitc_init:
|
||||||
cbr_opts += ['-c']
|
cbr_opts += ['-c']
|
||||||
group.add_option(*cbr_opts,
|
group.add_option(*cbr_opts, default=True,
|
||||||
dest='current_branch_only', action='store_true',
|
dest='current_branch_only', action='store_true',
|
||||||
help='fetch only current manifest branch from server')
|
help='fetch only current manifest branch from server (default)')
|
||||||
group.add_option('--no-current-branch',
|
group.add_option('--no-current-branch',
|
||||||
dest='current_branch_only', action='store_false',
|
dest='current_branch_only', action='store_false',
|
||||||
help='fetch all manifest branches from server')
|
help='fetch all manifest branches from server')
|
||||||
@ -612,15 +615,20 @@ def _Init(args, gitc_init=False):
|
|||||||
try:
|
try:
|
||||||
if not opt.quiet:
|
if not opt.quiet:
|
||||||
print('Downloading Repo source from', url)
|
print('Downloading Repo source from', url)
|
||||||
dst = os.path.abspath(os.path.join(repodir, S_repo))
|
dst_final = os.path.abspath(os.path.join(repodir, S_repo))
|
||||||
|
dst = dst_final + '.tmp'
|
||||||
|
shutil.rmtree(dst, ignore_errors=True)
|
||||||
_Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose)
|
_Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose)
|
||||||
|
|
||||||
remote_ref, rev = check_repo_rev(dst, rev, opt.repo_verify, quiet=opt.quiet)
|
remote_ref, rev = check_repo_rev(dst, rev, opt.repo_verify, quiet=opt.quiet)
|
||||||
_Checkout(dst, remote_ref, rev, opt.quiet)
|
_Checkout(dst, remote_ref, rev, opt.quiet)
|
||||||
|
|
||||||
if not os.path.isfile(os.path.join(dst, 'repo')):
|
if not os.path.isfile(os.path.join(dst, 'repo')):
|
||||||
print("warning: '%s' does not look like a git-repo repository, is "
|
print("fatal: '%s' does not look like a git-repo repository, is "
|
||||||
"REPO_URL set correctly?" % url, file=sys.stderr)
|
"--repo-url set correctly?" % url, file=sys.stderr)
|
||||||
|
raise CloneFailure()
|
||||||
|
|
||||||
|
os.rename(dst, dst_final)
|
||||||
|
|
||||||
except CloneFailure:
|
except CloneFailure:
|
||||||
print('fatal: double check your --repo-rev setting.', file=sys.stderr)
|
print('fatal: double check your --repo-rev setting.', file=sys.stderr)
|
||||||
@ -1317,6 +1325,7 @@ def main(orig_args):
|
|||||||
print("fatal: cloning the git-repo repository failed, will remove "
|
print("fatal: cloning the git-repo repository failed, will remove "
|
||||||
"'%s' " % path, file=sys.stderr)
|
"'%s' " % path, file=sys.stderr)
|
||||||
shutil.rmtree(path, ignore_errors=True)
|
shutil.rmtree(path, ignore_errors=True)
|
||||||
|
shutil.rmtree(path + '.tmp', ignore_errors=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
repo_main, rel_repo_dir = _FindRepo()
|
repo_main, rel_repo_dir = _FindRepo()
|
||||||
else:
|
else:
|
||||||
|
@ -60,8 +60,10 @@ change id will be added.
|
|||||||
capture_stderr=True)
|
capture_stderr=True)
|
||||||
status = p.Wait()
|
status = p.Wait()
|
||||||
|
|
||||||
print(p.stdout, file=sys.stdout)
|
if p.stdout:
|
||||||
print(p.stderr, file=sys.stderr)
|
print(p.stdout.strip(), file=sys.stdout)
|
||||||
|
if p.stderr:
|
||||||
|
print(p.stderr.strip(), file=sys.stderr)
|
||||||
|
|
||||||
if status == 0:
|
if status == 0:
|
||||||
# The cherry-pick was applied correctly. We just need to edit the
|
# The cherry-pick was applied correctly. We just need to edit the
|
||||||
|
@ -118,6 +118,16 @@ synced and their revisions won't be found.
|
|||||||
self.printRevision(project.revisionExpr)
|
self.printRevision(project.revisionExpr)
|
||||||
self.out.nl()
|
self.out.nl()
|
||||||
|
|
||||||
|
if diff['missing']:
|
||||||
|
self.out.nl()
|
||||||
|
self.printText('missing projects : \n')
|
||||||
|
self.out.nl()
|
||||||
|
for project in diff['missing']:
|
||||||
|
self.printProject('\t%s' % (project.relpath))
|
||||||
|
self.printText(' at revision ')
|
||||||
|
self.printRevision(project.revisionExpr)
|
||||||
|
self.out.nl()
|
||||||
|
|
||||||
if diff['changed']:
|
if diff['changed']:
|
||||||
self.out.nl()
|
self.out.nl()
|
||||||
self.printText('changed projects : \n')
|
self.printText('changed projects : \n')
|
||||||
|
@ -109,6 +109,10 @@ to update the working directory files.
|
|||||||
Args:
|
Args:
|
||||||
opt: options from optparse.
|
opt: options from optparse.
|
||||||
"""
|
"""
|
||||||
|
# Normally this value is set when instantiating the project, but the
|
||||||
|
# manifest project is special and is created when instantiating the
|
||||||
|
# manifest which happens before we parse options.
|
||||||
|
self.manifest.manifestProject.clone_depth = opt.manifest_depth
|
||||||
if not self.manifest.manifestProject.Sync(
|
if not self.manifest.manifestProject.Sync(
|
||||||
manifest_url=opt.manifest_url,
|
manifest_url=opt.manifest_url,
|
||||||
manifest_branch=opt.manifest_branch,
|
manifest_branch=opt.manifest_branch,
|
||||||
@ -144,7 +148,7 @@ to update the working directory files.
|
|||||||
return value
|
return value
|
||||||
return a
|
return a
|
||||||
|
|
||||||
def _ShouldConfigureUser(self, opt):
|
def _ShouldConfigureUser(self, opt, existing_checkout):
|
||||||
gc = self.client.globalConfig
|
gc = self.client.globalConfig
|
||||||
mp = self.manifest.manifestProject
|
mp = self.manifest.manifestProject
|
||||||
|
|
||||||
@ -156,7 +160,7 @@ to update the working directory files.
|
|||||||
mp.config.SetString('user.name', gc.GetString('user.name'))
|
mp.config.SetString('user.name', gc.GetString('user.name'))
|
||||||
mp.config.SetString('user.email', gc.GetString('user.email'))
|
mp.config.SetString('user.email', gc.GetString('user.email'))
|
||||||
|
|
||||||
if not opt.quiet:
|
if not opt.quiet and not existing_checkout or opt.verbose:
|
||||||
print()
|
print()
|
||||||
print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
|
print('Your identity is: %s <%s>' % (mp.config.GetString('user.name'),
|
||||||
mp.config.GetString('user.email')))
|
mp.config.GetString('user.email')))
|
||||||
@ -241,7 +245,7 @@ to update the working directory files.
|
|||||||
if current_dir != self.manifest.topdir:
|
if current_dir != self.manifest.topdir:
|
||||||
print('If this is not the directory in which you want to initialize '
|
print('If this is not the directory in which you want to initialize '
|
||||||
'repo, please run:')
|
'repo, please run:')
|
||||||
print(' rm -r %s/.repo' % self.manifest.topdir)
|
print(' rm -r %s' % os.path.join(self.manifest.topdir, '.repo'))
|
||||||
print('and try again.')
|
print('and try again.')
|
||||||
|
|
||||||
def ValidateOptions(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
@ -311,10 +315,17 @@ to update the working directory files.
|
|||||||
# Older versions of git supported worktree, but had dangerous gc bugs.
|
# Older versions of git supported worktree, but had dangerous gc bugs.
|
||||||
git_require((2, 15, 0), fail=True, msg='git gc worktree corruption')
|
git_require((2, 15, 0), fail=True, msg='git gc worktree corruption')
|
||||||
|
|
||||||
|
# Provide a short notice that we're reinitializing an existing checkout.
|
||||||
|
# Sometimes developers might not realize that they're in one, or that
|
||||||
|
# repo doesn't do nested checkouts.
|
||||||
|
existing_checkout = self.manifest.manifestProject.Exists
|
||||||
|
if not opt.quiet and existing_checkout:
|
||||||
|
print('repo: reusing existing repo client checkout in', self.manifest.topdir)
|
||||||
|
|
||||||
self._SyncManifest(opt)
|
self._SyncManifest(opt)
|
||||||
|
|
||||||
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
|
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
|
||||||
if opt.config_name or self._ShouldConfigureUser(opt):
|
if opt.config_name or self._ShouldConfigureUser(opt, existing_checkout):
|
||||||
self._ConfigureUser(opt)
|
self._ConfigureUser(opt)
|
||||||
self._ConfigureColor()
|
self._ConfigureColor()
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ need to be performed by an end-user.
|
|||||||
_PostRepoUpgrade(self.manifest)
|
_PostRepoUpgrade(self.manifest)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not rp.Sync_NetworkHalf():
|
if not rp.Sync_NetworkHalf().success:
|
||||||
print("error: can't update repo", file=sys.stderr)
|
print("error: can't update repo", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ The '%prog' command stages files to prepare the next commit.
|
|||||||
out.nl()
|
out.nl()
|
||||||
|
|
||||||
out.prompt('project> ')
|
out.prompt('project> ')
|
||||||
|
out.flush()
|
||||||
try:
|
try:
|
||||||
a = sys.stdin.readline()
|
a = sys.stdin.readline()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
272
subcmds/sync.py
272
subcmds/sync.py
@ -21,13 +21,16 @@ import multiprocessing
|
|||||||
import netrc
|
import netrc
|
||||||
from optparse import SUPPRESS_HELP
|
from optparse import SUPPRESS_HELP
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
from typing import NamedTuple
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
import xml.parsers.expat
|
||||||
import xmlrpc.client
|
import xmlrpc.client
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -52,20 +55,76 @@ import git_superproject
|
|||||||
import gitc_utils
|
import gitc_utils
|
||||||
from project import Project
|
from project import Project
|
||||||
from project import RemoteSpec
|
from project import RemoteSpec
|
||||||
from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE
|
from command import Command, DEFAULT_LOCAL_JOBS, MirrorSafeCommand, WORKER_BATCH_SIZE
|
||||||
from error import RepoChangedException, GitError, ManifestParseError
|
from error import RepoChangedException, GitError, ManifestParseError
|
||||||
import platform_utils
|
import platform_utils
|
||||||
from project import SyncBuffer
|
from project import SyncBuffer
|
||||||
from progress import Progress
|
from progress import Progress
|
||||||
|
from repo_trace import IsTrace, Trace
|
||||||
import ssh
|
import ssh
|
||||||
from wrapper import Wrapper
|
from wrapper import Wrapper
|
||||||
from manifest_xml import GitcManifest
|
from manifest_xml import GitcManifest
|
||||||
|
|
||||||
_ONE_DAY_S = 24 * 60 * 60
|
_ONE_DAY_S = 24 * 60 * 60
|
||||||
|
# Env var to implicitly turn off object backups.
|
||||||
|
REPO_BACKUP_OBJECTS = 'REPO_BACKUP_OBJECTS'
|
||||||
|
|
||||||
|
_BACKUP_OBJECTS = os.environ.get(REPO_BACKUP_OBJECTS) != '0'
|
||||||
|
|
||||||
|
|
||||||
|
class _FetchOneResult(NamedTuple):
|
||||||
|
"""_FetchOne return value.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): True if successful.
|
||||||
|
project (Project): The fetched project.
|
||||||
|
start (float): The starting time.time().
|
||||||
|
finish (float): The ending time.time().
|
||||||
|
remote_fetched (bool): True if the remote was actually queried.
|
||||||
|
"""
|
||||||
|
success: bool
|
||||||
|
project: Project
|
||||||
|
start: float
|
||||||
|
finish: float
|
||||||
|
remote_fetched: bool
|
||||||
|
|
||||||
|
|
||||||
|
class _FetchResult(NamedTuple):
|
||||||
|
"""_Fetch return value.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): True if successful.
|
||||||
|
projects (set[str]): The names of the git directories of fetched projects.
|
||||||
|
"""
|
||||||
|
success: bool
|
||||||
|
projects: set[str]
|
||||||
|
|
||||||
|
|
||||||
|
class _FetchMainResult(NamedTuple):
|
||||||
|
"""_FetchMain return value.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
all_projects (list[Project]): The fetched projects.
|
||||||
|
"""
|
||||||
|
all_projects: list[Project]
|
||||||
|
|
||||||
|
|
||||||
|
class _CheckoutOneResult(NamedTuple):
|
||||||
|
"""_CheckoutOne return value.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): True if successful.
|
||||||
|
project (Project): The project.
|
||||||
|
start (float): The starting time.time().
|
||||||
|
finish (float): The ending time.time().
|
||||||
|
"""
|
||||||
|
success: bool
|
||||||
|
project: Project
|
||||||
|
start: float
|
||||||
|
finish: float
|
||||||
|
|
||||||
|
|
||||||
class Sync(Command, MirrorSafeCommand):
|
class Sync(Command, MirrorSafeCommand):
|
||||||
jobs = 1
|
|
||||||
COMMON = True
|
COMMON = True
|
||||||
MULTI_MANIFEST_SUPPORT = True
|
MULTI_MANIFEST_SUPPORT = True
|
||||||
helpSummary = "Update working tree to the latest revision"
|
helpSummary = "Update working tree to the latest revision"
|
||||||
@ -168,21 +227,16 @@ If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
|
|||||||
later is required to fix a server side protocol bug.
|
later is required to fix a server side protocol bug.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
PARALLEL_JOBS = 1
|
# A value of 0 means we want parallel jobs, but we'll determine the default
|
||||||
|
# value later on.
|
||||||
def _CommonOptions(self, p):
|
PARALLEL_JOBS = 0
|
||||||
if self.outer_client and self.outer_client.manifest:
|
|
||||||
try:
|
|
||||||
self.PARALLEL_JOBS = self.outer_client.manifest.default.sync_j
|
|
||||||
except ManifestParseError:
|
|
||||||
pass
|
|
||||||
super()._CommonOptions(p)
|
|
||||||
|
|
||||||
def _Options(self, p, show_smart=True):
|
def _Options(self, p, show_smart=True):
|
||||||
p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
|
p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
|
||||||
help='number of network jobs to run in parallel (defaults to --jobs)')
|
help='number of network jobs to run in parallel (defaults to --jobs or 1)')
|
||||||
p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
|
p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
|
||||||
help='number of local checkout jobs to run in parallel (defaults to --jobs)')
|
help='number of local checkout jobs to run in parallel (defaults to --jobs or '
|
||||||
|
f'{DEFAULT_LOCAL_JOBS})')
|
||||||
|
|
||||||
p.add_option('-f', '--force-broken',
|
p.add_option('-f', '--force-broken',
|
||||||
dest='force_broken', action='store_true',
|
dest='force_broken', action='store_true',
|
||||||
@ -411,7 +465,7 @@ later is required to fix a server side protocol bug.
|
|||||||
success = False
|
success = False
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
try:
|
try:
|
||||||
success = project.Sync_NetworkHalf(
|
sync_result = project.Sync_NetworkHalf(
|
||||||
quiet=opt.quiet,
|
quiet=opt.quiet,
|
||||||
verbose=opt.verbose,
|
verbose=opt.verbose,
|
||||||
output_redir=buf,
|
output_redir=buf,
|
||||||
@ -425,6 +479,7 @@ later is required to fix a server side protocol bug.
|
|||||||
ssh_proxy=self.ssh_proxy,
|
ssh_proxy=self.ssh_proxy,
|
||||||
clone_filter=project.manifest.CloneFilter,
|
clone_filter=project.manifest.CloneFilter,
|
||||||
partial_clone_exclude=project.manifest.PartialCloneExclude)
|
partial_clone_exclude=project.manifest.PartialCloneExclude)
|
||||||
|
success = sync_result.success
|
||||||
|
|
||||||
output = buf.getvalue()
|
output = buf.getvalue()
|
||||||
if (opt.verbose or not success) and output:
|
if (opt.verbose or not success) and output:
|
||||||
@ -442,7 +497,8 @@ later is required to fix a server side protocol bug.
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
finish = time.time()
|
finish = time.time()
|
||||||
return (success, project, start, finish)
|
return _FetchOneResult(success, project, start, finish,
|
||||||
|
sync_result.remote_fetched)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _FetchInitChild(cls, ssh_proxy):
|
def _FetchInitChild(cls, ssh_proxy):
|
||||||
@ -451,8 +507,9 @@ later is required to fix a server side protocol bug.
|
|||||||
def _Fetch(self, projects, opt, err_event, ssh_proxy):
|
def _Fetch(self, projects, opt, err_event, ssh_proxy):
|
||||||
ret = True
|
ret = True
|
||||||
|
|
||||||
jobs = opt.jobs_network if opt.jobs_network else self.jobs
|
jobs = opt.jobs_network
|
||||||
fetched = set()
|
fetched = set()
|
||||||
|
remote_fetched = set()
|
||||||
pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
|
pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
|
||||||
|
|
||||||
objdir_project_map = dict()
|
objdir_project_map = dict()
|
||||||
@ -463,10 +520,16 @@ later is required to fix a server side protocol bug.
|
|||||||
def _ProcessResults(results_sets):
|
def _ProcessResults(results_sets):
|
||||||
ret = True
|
ret = True
|
||||||
for results in results_sets:
|
for results in results_sets:
|
||||||
for (success, project, start, finish) in results:
|
for result in results:
|
||||||
|
success = result.success
|
||||||
|
project = result.project
|
||||||
|
start = result.start
|
||||||
|
finish = result.finish
|
||||||
self._fetch_times.Set(project, finish - start)
|
self._fetch_times.Set(project, finish - start)
|
||||||
self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
|
self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
|
||||||
start, finish, success)
|
start, finish, success)
|
||||||
|
if result.remote_fetched:
|
||||||
|
remote_fetched.add(project)
|
||||||
# Check for any errors before running any more tasks.
|
# Check for any errors before running any more tasks.
|
||||||
# ...we'll let existing jobs finish, though.
|
# ...we'll let existing jobs finish, though.
|
||||||
if not success:
|
if not success:
|
||||||
@ -524,7 +587,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if not self.outer_client.manifest.IsArchive:
|
if not self.outer_client.manifest.IsArchive:
|
||||||
self._GCProjects(projects, opt, err_event)
|
self._GCProjects(projects, opt, err_event)
|
||||||
|
|
||||||
return (ret, fetched)
|
return _FetchResult(ret, fetched)
|
||||||
|
|
||||||
def _FetchMain(self, opt, args, all_projects, err_event,
|
def _FetchMain(self, opt, args, all_projects, err_event,
|
||||||
ssh_proxy, manifest):
|
ssh_proxy, manifest):
|
||||||
@ -550,7 +613,9 @@ later is required to fix a server side protocol bug.
|
|||||||
to_fetch.extend(all_projects)
|
to_fetch.extend(all_projects)
|
||||||
to_fetch.sort(key=self._fetch_times.Get, reverse=True)
|
to_fetch.sort(key=self._fetch_times.Get, reverse=True)
|
||||||
|
|
||||||
success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
|
result = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
|
||||||
|
success = result.success
|
||||||
|
fetched = result.projects
|
||||||
if not success:
|
if not success:
|
||||||
err_event.set()
|
err_event.set()
|
||||||
|
|
||||||
@ -560,7 +625,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if err_event.is_set():
|
if err_event.is_set():
|
||||||
print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
|
print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return
|
return _FetchMainResult([])
|
||||||
|
|
||||||
# Iteratively fetch missing and/or nested unregistered submodules
|
# Iteratively fetch missing and/or nested unregistered submodules
|
||||||
previously_missing_set = set()
|
previously_missing_set = set()
|
||||||
@ -583,12 +648,14 @@ later is required to fix a server side protocol bug.
|
|||||||
if previously_missing_set == missing_set:
|
if previously_missing_set == missing_set:
|
||||||
break
|
break
|
||||||
previously_missing_set = missing_set
|
previously_missing_set = missing_set
|
||||||
success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy)
|
result = self._Fetch(missing, opt, err_event, ssh_proxy)
|
||||||
|
success = result.success
|
||||||
|
new_fetched = result.projects
|
||||||
if not success:
|
if not success:
|
||||||
err_event.set()
|
err_event.set()
|
||||||
fetched.update(new_fetched)
|
fetched.update(new_fetched)
|
||||||
|
|
||||||
return all_projects
|
return _FetchMainResult(all_projects)
|
||||||
|
|
||||||
def _CheckoutOne(self, detach_head, force_sync, project):
|
def _CheckoutOne(self, detach_head, force_sync, project):
|
||||||
"""Checkout work tree for one project
|
"""Checkout work tree for one project
|
||||||
@ -620,7 +687,7 @@ later is required to fix a server side protocol bug.
|
|||||||
if not success:
|
if not success:
|
||||||
print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
|
print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
|
||||||
finish = time.time()
|
finish = time.time()
|
||||||
return (success, project, start, finish)
|
return _CheckoutOneResult(success, project, start, finish)
|
||||||
|
|
||||||
def _Checkout(self, all_projects, opt, err_results):
|
def _Checkout(self, all_projects, opt, err_results):
|
||||||
"""Checkout projects listed in all_projects
|
"""Checkout projects listed in all_projects
|
||||||
@ -635,7 +702,11 @@ later is required to fix a server side protocol bug.
|
|||||||
|
|
||||||
def _ProcessResults(pool, pm, results):
|
def _ProcessResults(pool, pm, results):
|
||||||
ret = True
|
ret = True
|
||||||
for (success, project, start, finish) in results:
|
for result in results:
|
||||||
|
success = result.success
|
||||||
|
project = result.project
|
||||||
|
start = result.start
|
||||||
|
finish = result.finish
|
||||||
self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
|
self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
|
||||||
start, finish, success)
|
start, finish, success)
|
||||||
# Check for any errors before running any more tasks.
|
# Check for any errors before running any more tasks.
|
||||||
@ -651,33 +722,69 @@ later is required to fix a server side protocol bug.
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
return self.ExecuteInParallel(
|
return self.ExecuteInParallel(
|
||||||
opt.jobs_checkout if opt.jobs_checkout else self.jobs,
|
opt.jobs_checkout,
|
||||||
functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
|
functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
|
||||||
all_projects,
|
all_projects,
|
||||||
callback=_ProcessResults,
|
callback=_ProcessResults,
|
||||||
output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
|
output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
|
||||||
|
|
||||||
|
def _backup_cruft(self, bare_git):
|
||||||
|
"""Save a copy of any cruft from `git gc`."""
|
||||||
|
# Find any cruft packs in the current gitdir, and save them.
|
||||||
|
# b/221065125 (repo sync complains that objects are missing). This does
|
||||||
|
# not prevent that state, but makes it so that the missing objects are
|
||||||
|
# available.
|
||||||
|
objdir = bare_git._project.objdir
|
||||||
|
pack_dir = os.path.join(objdir, 'pack')
|
||||||
|
bak_dir = os.path.join(objdir, '.repo', 'pack.bak')
|
||||||
|
if not _BACKUP_OBJECTS or not platform_utils.isdir(pack_dir):
|
||||||
|
return
|
||||||
|
saved = []
|
||||||
|
files = set(platform_utils.listdir(pack_dir))
|
||||||
|
to_backup = []
|
||||||
|
for f in files:
|
||||||
|
base, ext = os.path.splitext(f)
|
||||||
|
if base + '.mtimes' in files:
|
||||||
|
to_backup.append(f)
|
||||||
|
if to_backup:
|
||||||
|
os.makedirs(bak_dir, exist_ok=True)
|
||||||
|
for fname in to_backup:
|
||||||
|
bak_fname = os.path.join(bak_dir, fname)
|
||||||
|
if not os.path.exists(bak_fname):
|
||||||
|
saved.append(fname)
|
||||||
|
# Use a tmp file so that we are sure of a complete copy.
|
||||||
|
shutil.copy(os.path.join(pack_dir, fname), bak_fname + '.tmp')
|
||||||
|
shutil.move(bak_fname + '.tmp', bak_fname)
|
||||||
|
if saved:
|
||||||
|
Trace('%s saved %s', bare_git._project.name, ' '.join(saved))
|
||||||
|
|
||||||
def _GCProjects(self, projects, opt, err_event):
|
def _GCProjects(self, projects, opt, err_event):
|
||||||
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')
|
||||||
|
|
||||||
tidy_dirs = {}
|
tidy_dirs = {}
|
||||||
for project in projects:
|
for project in projects:
|
||||||
# Make sure pruning never kicks in with shared projects.
|
# Make sure pruning never kicks in with shared projects that do not use
|
||||||
|
# alternates to avoid corruption.
|
||||||
if (not project.use_git_worktrees and
|
if (not project.use_git_worktrees and
|
||||||
len(project.manifest.GetProjectsWithName(project.name, all_manifests=True)) > 1):
|
len(project.manifest.GetProjectsWithName(project.name, all_manifests=True)) > 1):
|
||||||
if not opt.quiet:
|
if project.UseAlternates:
|
||||||
print('\r%s: Shared project %s found, disabling pruning.' %
|
# Undo logic set by previous versions of repo.
|
||||||
(project.relpath, project.name))
|
project.config.SetString('extensions.preciousObjects', None)
|
||||||
if git_require((2, 7, 0)):
|
project.config.SetString('gc.pruneExpire', None)
|
||||||
project.EnableRepositoryExtension('preciousObjects')
|
|
||||||
else:
|
else:
|
||||||
# This isn't perfect, but it's the best we can do with old git.
|
if not opt.quiet:
|
||||||
print('\r%s: WARNING: shared projects are unreliable when using old '
|
print('\r%s: Shared project %s found, disabling pruning.' %
|
||||||
'versions of git; please upgrade to git-2.7.0+.'
|
(project.relpath, project.name))
|
||||||
% (project.relpath,),
|
if git_require((2, 7, 0)):
|
||||||
file=sys.stderr)
|
project.EnableRepositoryExtension('preciousObjects')
|
||||||
project.config.SetString('gc.pruneExpire', 'never')
|
else:
|
||||||
|
# This isn't perfect, but it's the best we can do with old git.
|
||||||
|
print('\r%s: WARNING: shared projects are unreliable when using old '
|
||||||
|
'versions of git; please upgrade to git-2.7.0+.'
|
||||||
|
% (project.relpath,),
|
||||||
|
file=sys.stderr)
|
||||||
|
project.config.SetString('gc.pruneExpire', 'never')
|
||||||
project.config.SetString('gc.autoDetach', 'false')
|
project.config.SetString('gc.autoDetach', 'false')
|
||||||
# Only call git gc once per objdir, but call pack-refs for the remainder.
|
# Only call git gc once per objdir, but call pack-refs for the remainder.
|
||||||
if project.objdir not in tidy_dirs:
|
if project.objdir not in tidy_dirs:
|
||||||
@ -691,19 +798,28 @@ later is required to fix a server side protocol bug.
|
|||||||
project.bare_git,
|
project.bare_git,
|
||||||
)
|
)
|
||||||
|
|
||||||
cpu_count = os.cpu_count()
|
jobs = opt.jobs
|
||||||
jobs = min(self.jobs, cpu_count)
|
|
||||||
|
|
||||||
|
gc_args = ['--auto']
|
||||||
|
backup_cruft = False
|
||||||
|
if git_require((2, 37, 0)):
|
||||||
|
gc_args.append('--cruft')
|
||||||
|
backup_cruft = True
|
||||||
|
pack_refs_args = ()
|
||||||
if jobs < 2:
|
if jobs < 2:
|
||||||
for (run_gc, bare_git) in tidy_dirs.values():
|
for (run_gc, bare_git) in tidy_dirs.values():
|
||||||
pm.update(msg=bare_git._project.name)
|
pm.update(msg=bare_git._project.name)
|
||||||
|
|
||||||
if run_gc:
|
if run_gc:
|
||||||
bare_git.gc('--auto')
|
bare_git.gc(*gc_args)
|
||||||
else:
|
else:
|
||||||
bare_git.pack_refs()
|
bare_git.pack_refs(*pack_refs_args)
|
||||||
|
if backup_cruft:
|
||||||
|
self._backup_cruft(bare_git)
|
||||||
pm.end()
|
pm.end()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
cpu_count = os.cpu_count()
|
||||||
config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
|
config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
|
||||||
|
|
||||||
threads = set()
|
threads = set()
|
||||||
@ -714,15 +830,17 @@ later is required to fix a server side protocol bug.
|
|||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
if run_gc:
|
if run_gc:
|
||||||
bare_git.gc('--auto', config=config)
|
bare_git.gc(*gc_args, config=config)
|
||||||
else:
|
else:
|
||||||
bare_git.pack_refs(config=config)
|
bare_git.pack_refs(*pack_refs_args, config=config)
|
||||||
except GitError:
|
except GitError:
|
||||||
err_event.set()
|
err_event.set()
|
||||||
except Exception:
|
except Exception:
|
||||||
err_event.set()
|
err_event.set()
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
if backup_cruft:
|
||||||
|
self._backup_cruft(bare_git)
|
||||||
pm.finish(bare_git._project.name)
|
pm.finish(bare_git._project.name)
|
||||||
sem.release()
|
sem.release()
|
||||||
|
|
||||||
@ -1011,9 +1129,6 @@ later is required to fix a server side protocol bug.
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
self._ReloadManifest(manifest_name, mp.manifest)
|
self._ReloadManifest(manifest_name, mp.manifest)
|
||||||
|
|
||||||
if opt.jobs is None:
|
|
||||||
self.jobs = mp.manifest.default.sync_j
|
|
||||||
|
|
||||||
def ValidateOptions(self, opt, args):
|
def ValidateOptions(self, opt, args):
|
||||||
if opt.force_broken:
|
if opt.force_broken:
|
||||||
print('warning: -f/--force-broken is now the default behavior, and the '
|
print('warning: -f/--force-broken is now the default behavior, and the '
|
||||||
@ -1036,12 +1151,6 @@ later is required to fix a server side protocol bug.
|
|||||||
opt.prune = True
|
opt.prune = True
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
if opt.jobs:
|
|
||||||
self.jobs = opt.jobs
|
|
||||||
if self.jobs > 1:
|
|
||||||
soft_limit, _ = _rlimit_nofile()
|
|
||||||
self.jobs = min(self.jobs, (soft_limit - 5) // 3)
|
|
||||||
|
|
||||||
manifest = self.outer_manifest
|
manifest = self.outer_manifest
|
||||||
if not opt.outer_manifest:
|
if not opt.outer_manifest:
|
||||||
manifest = self.manifest
|
manifest = self.manifest
|
||||||
@ -1079,19 +1188,48 @@ later is required to fix a server side protocol bug.
|
|||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
|
|
||||||
for m in self.ManifestList(opt):
|
for m in self.ManifestList(opt):
|
||||||
mp = m.manifestProject
|
if not m.manifestProject.standalone_manifest_url:
|
||||||
is_standalone_manifest = bool(mp.standalone_manifest_url)
|
m.manifestProject.PreSync()
|
||||||
if not is_standalone_manifest:
|
|
||||||
mp.PreSync()
|
|
||||||
|
|
||||||
if opt.repo_upgraded:
|
if opt.repo_upgraded:
|
||||||
_PostRepoUpgrade(m, quiet=opt.quiet)
|
_PostRepoUpgrade(manifest, quiet=opt.quiet)
|
||||||
|
|
||||||
|
mp = manifest.manifestProject
|
||||||
if opt.mp_update:
|
if opt.mp_update:
|
||||||
self._UpdateAllManifestProjects(opt, mp, manifest_name)
|
self._UpdateAllManifestProjects(opt, mp, manifest_name)
|
||||||
else:
|
else:
|
||||||
print('Skipping update of local manifest project.')
|
print('Skipping update of local manifest project.')
|
||||||
|
|
||||||
|
# Now that the manifests are up-to-date, setup the jobs value.
|
||||||
|
if opt.jobs is None:
|
||||||
|
# User has not made a choice, so use the manifest settings.
|
||||||
|
opt.jobs = mp.default.sync_j
|
||||||
|
if opt.jobs is not None:
|
||||||
|
# Neither user nor manifest have made a choice.
|
||||||
|
if opt.jobs_network is None:
|
||||||
|
opt.jobs_network = opt.jobs
|
||||||
|
if opt.jobs_checkout is None:
|
||||||
|
opt.jobs_checkout = opt.jobs
|
||||||
|
# Setup defaults if jobs==0.
|
||||||
|
if not opt.jobs:
|
||||||
|
if not opt.jobs_network:
|
||||||
|
opt.jobs_network = 1
|
||||||
|
if not opt.jobs_checkout:
|
||||||
|
opt.jobs_checkout = DEFAULT_LOCAL_JOBS
|
||||||
|
opt.jobs = os.cpu_count()
|
||||||
|
|
||||||
|
# Try to stay under user rlimit settings.
|
||||||
|
#
|
||||||
|
# Since each worker requires at 3 file descriptors to run `git fetch`, use
|
||||||
|
# that to scale down the number of jobs. Unfortunately there isn't an easy
|
||||||
|
# way to determine this reliably as systems change, but it was last measured
|
||||||
|
# by hand in 2011.
|
||||||
|
soft_limit, _ = _rlimit_nofile()
|
||||||
|
jobs_soft_limit = max(1, (soft_limit - 5) // 3)
|
||||||
|
opt.jobs = min(opt.jobs, jobs_soft_limit)
|
||||||
|
opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
|
||||||
|
opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
|
||||||
|
|
||||||
superproject_logging_data = {}
|
superproject_logging_data = {}
|
||||||
self._UpdateProjectsRevisionId(opt, args, superproject_logging_data,
|
self._UpdateProjectsRevisionId(opt, args, superproject_logging_data,
|
||||||
manifest)
|
manifest)
|
||||||
@ -1146,8 +1284,9 @@ later is required to fix a server side protocol bug.
|
|||||||
with ssh.ProxyManager(manager) as ssh_proxy:
|
with ssh.ProxyManager(manager) as ssh_proxy:
|
||||||
# Initialize the socket dir once in the parent.
|
# Initialize the socket dir once in the parent.
|
||||||
ssh_proxy.sock()
|
ssh_proxy.sock()
|
||||||
all_projects = self._FetchMain(opt, args, all_projects, err_event,
|
result = self._FetchMain(opt, args, all_projects, err_event,
|
||||||
ssh_proxy, manifest)
|
ssh_proxy, manifest)
|
||||||
|
all_projects = result.all_projects
|
||||||
|
|
||||||
if opt.network_only:
|
if opt.network_only:
|
||||||
return
|
return
|
||||||
@ -1410,11 +1549,16 @@ class PersistentTransport(xmlrpc.client.Transport):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
p, u = xmlrpc.client.getparser()
|
p, u = xmlrpc.client.getparser()
|
||||||
while 1:
|
# Response should be fairly small, so read it all at once.
|
||||||
data = response.read(1024)
|
# This way we can show it to the user in case of error (e.g. HTML).
|
||||||
if not data:
|
data = response.read()
|
||||||
break
|
try:
|
||||||
p.feed(data)
|
p.feed(data)
|
||||||
|
except xml.parsers.expat.ExpatError as e:
|
||||||
|
raise IOError(
|
||||||
|
f'Parsing the manifest failed: {e}\n'
|
||||||
|
f'Please report this to your manifest server admin.\n'
|
||||||
|
f'Here is the full response:\n{data.decode("utf-8")}')
|
||||||
p.close()
|
p.close()
|
||||||
return u.close()
|
return u.close()
|
||||||
|
|
||||||
|
@ -78,6 +78,13 @@ added to the respective list of users, and emails are sent to any
|
|||||||
new users. Users passed as --reviewers must already be registered
|
new users. Users passed as --reviewers must already be registered
|
||||||
with the code review system, or the upload will fail.
|
with the code review system, or the upload will fail.
|
||||||
|
|
||||||
|
While most normal Gerrit options have dedicated command line options,
|
||||||
|
direct access to the Gerit options is available via --push-options.
|
||||||
|
This is useful when Gerrit has newer functionality that %prog doesn't
|
||||||
|
yet support, or doesn't have plans to support. See the Push Options
|
||||||
|
documentation for more details:
|
||||||
|
https://gerrit-review.googlesource.com/Documentation/user-upload.html#push_options
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
review.URL.autoupload:
|
review.URL.autoupload:
|
||||||
@ -190,6 +197,9 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
p.add_option('-w', '--wip',
|
p.add_option('-w', '--wip',
|
||||||
action='store_true', dest='wip', default=False,
|
action='store_true', dest='wip', default=False,
|
||||||
help='upload as a work-in-progress change')
|
help='upload as a work-in-progress change')
|
||||||
|
p.add_option('-r', '--ready',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='mark change as ready (clears work-in-progress setting)')
|
||||||
p.add_option('-o', '--push-option',
|
p.add_option('-o', '--push-option',
|
||||||
type='string', action='append', dest='push_options',
|
type='string', action='append', dest='push_options',
|
||||||
default=[],
|
default=[],
|
||||||
@ -252,7 +262,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
answer = sys.stdin.readline().strip().lower()
|
answer = sys.stdin.readline().strip().lower()
|
||||||
answer = answer in ('y', 'yes', '1', 'true', 't')
|
answer = answer in ('y', 'yes', '1', 'true', 't')
|
||||||
|
|
||||||
if answer:
|
if not opt.yes and answer:
|
||||||
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
|
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
|
||||||
answer = _ConfirmManyUploads()
|
answer = _ConfirmManyUploads()
|
||||||
|
|
||||||
@ -325,14 +335,15 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
if not todo:
|
if not todo:
|
||||||
_die("nothing uncommented for upload")
|
_die("nothing uncommented for upload")
|
||||||
|
|
||||||
many_commits = False
|
if not opt.yes:
|
||||||
for branch in todo:
|
many_commits = False
|
||||||
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
|
for branch in todo:
|
||||||
many_commits = True
|
if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD:
|
||||||
break
|
many_commits = True
|
||||||
if many_commits:
|
break
|
||||||
if not _ConfirmManyUploads(multiple_branches=True):
|
if many_commits:
|
||||||
_die("upload aborted by user")
|
if not _ConfirmManyUploads(multiple_branches=True):
|
||||||
|
_die("upload aborted by user")
|
||||||
|
|
||||||
self._UploadAndReport(opt, todo, people)
|
self._UploadAndReport(opt, todo, people)
|
||||||
|
|
||||||
@ -465,6 +476,7 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
private=opt.private,
|
private=opt.private,
|
||||||
notify=notify,
|
notify=notify,
|
||||||
wip=opt.wip,
|
wip=opt.wip,
|
||||||
|
ready=opt.ready,
|
||||||
dest_branch=destination,
|
dest_branch=destination,
|
||||||
validate_certs=opt.validate_certs,
|
validate_certs=opt.validate_certs,
|
||||||
push_options=opt.push_options)
|
push_options=opt.push_options)
|
||||||
|
@ -33,7 +33,7 @@ class Version(Command, MirrorSafeCommand):
|
|||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
rp = self.manifest.repoProject
|
rp = self.manifest.repoProject
|
||||||
rem = rp.GetRemote(rp.remote.name)
|
rem = rp.GetRemote()
|
||||||
branch = rp.GetBranch('default')
|
branch = rp.GetBranch('default')
|
||||||
|
|
||||||
# These might not be the same. Report them both.
|
# These might not be the same. Report them both.
|
||||||
|
@ -874,3 +874,27 @@ class ExtendProjectElementTests(ManifestParseTestCase):
|
|||||||
else:
|
else:
|
||||||
self.assertEqual(manifest.projects[0].relpath, 'bar')
|
self.assertEqual(manifest.projects[0].relpath, 'bar')
|
||||||
self.assertEqual(manifest.projects[1].relpath, 'y')
|
self.assertEqual(manifest.projects[1].relpath, 'y')
|
||||||
|
|
||||||
|
def test_extend_project_dest_branch(self):
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="default-remote" fetch="http://localhost" />
|
||||||
|
<default remote="default-remote" revision="refs/heads/main" dest-branch="foo" />
|
||||||
|
<project name="myproject" />
|
||||||
|
<extend-project name="myproject" dest-branch="bar" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(len(manifest.projects), 1)
|
||||||
|
self.assertEqual(manifest.projects[0].dest_branch, 'bar')
|
||||||
|
|
||||||
|
def test_extend_project_upstream(self):
|
||||||
|
manifest = self.getXmlManifest("""
|
||||||
|
<manifest>
|
||||||
|
<remote name="default-remote" fetch="http://localhost" />
|
||||||
|
<default remote="default-remote" revision="refs/heads/main" />
|
||||||
|
<project name="myproject" />
|
||||||
|
<extend-project name="myproject" upstream="bar" />
|
||||||
|
</manifest>
|
||||||
|
""")
|
||||||
|
self.assertEqual(len(manifest.projects), 1)
|
||||||
|
self.assertEqual(manifest.projects[0].upstream, 'bar')
|
||||||
|
Reference in New Issue
Block a user