Merge tag 'v2.46'
* tag 'v2.46':
project: ignore more curl failure modes
logging: Fix log formatting with colored output
upload: add a --topic option for setting topic explicitly
sync: Abort rebase in progress if force-checkout is set
ssh: Set git protocol version 2 on SSH ControlMaster
upload: drop check for uncommitted local changes
git: raise hard version to 1.9.1
release: update-hooks: helper for automatically syncing hooks
gitc: delete a few more dead references
man: regenerate man pages
Remove platform_utils.realpath
Fix drive mounted directory on Windows
git_command: unify soft/hard versions with requirements.json
Change-Id: I40ea02e54c0106265a34f88c65398c7519325b01
diff --git a/git_command.py b/git_command.py
index 09ed1a7..1ec7c3e 100644
--- a/git_command.py
+++ b/git_command.py
@@ -33,17 +33,6 @@
GIT = "git"
-# NB: These do not need to be kept in sync with the repo launcher script.
-# These may be much newer as it allows the repo launcher to roll between
-# different repo releases while source versions might require a newer git.
-#
-# The soft version is when we start warning users that the version is old and
-# we'll be dropping support for it. We'll refuse to work with versions older
-# than the hard version.
-#
-# git-1.7 is in (EOL) Ubuntu Precise. git-1.9 is in Ubuntu Trusty.
-MIN_GIT_VERSION_SOFT = (1, 9, 1)
-MIN_GIT_VERSION_HARD = (1, 7, 2)
GIT_DIR = "GIT_DIR"
LAST_GITDIR = None
diff --git a/hooks/commit-msg b/hooks/commit-msg
index 112df63..a6721d4 100755
--- a/hooks/commit-msg
+++ b/hooks/commit-msg
@@ -1,5 +1,8 @@
#!/bin/sh
-# From Gerrit Code Review 3.10.0 d5403dbf335ba7d48977fc95170c3f7027c34659
+# DO NOT EDIT THIS FILE
+# All updates should be sent upstream: https://gerrit.googlesource.com/gerrit/
+# This is synced from commit: 62f5bbea67f6dafa6e22a601a0c298214c510caf
+# DO NOT EDIT THIS FILE
#
# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
#
@@ -31,8 +34,7 @@
fi
# Do not create a change id if requested
-create_setting=$(git config --get gerrit.createChangeId)
-case "$create_setting" in
+case "$(git config --get gerrit.createChangeId)" in
false)
exit 0
;;
diff --git a/hooks/pre-auto-gc b/hooks/pre-auto-gc
index ec29be4..fb7b763 100755
--- a/hooks/pre-auto-gc
+++ b/hooks/pre-auto-gc
@@ -1,33 +1,25 @@
#!/bin/sh
+# DO NOT EDIT THIS FILE
+# All updates should be sent upstream: https://github.com/git/git
+# This is synced from commit: 00e10ef10e161a913893b8cb33aa080d4ca5baa6
+# DO NOT EDIT THIS FILE
#
# An example hook script to verify if you are on battery, in case you
-# are running Windows, Linux or OS X. Called by git-gc --auto with no
-# arguments. The hook should exit with non-zero status after issuing an
-# appropriate message if it wants to stop the auto repacking.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# are running Linux or OS X. Called by git-gc --auto with no arguments.
+# The hook should exit with non-zero status after issuing an appropriate
+# message if it wants to stop the auto repacking.
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# This hook is stored in the contrib/hooks directory. Your distribution
+# may have put this somewhere else. If you want to use this hook, you
+# should make this script executable then link to it in the repository
+# you would like to use it in.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-if uname -s | grep -q "_NT-"
-then
- if test -x $SYSTEMROOT/System32/Wbem/wmic
- then
- STATUS=$(wmic path win32_battery get batterystatus /format:list | tr -d '\r\n')
- [ "$STATUS" = "BatteryStatus=2" ] && exit 0 || exit 1
- fi
- exit 0
-fi
+# For example, if the hook is stored in
+# /usr/share/git-core/contrib/hooks/pre-auto-gc-battery:
+#
+# cd /path/to/your/repository.git
+# ln -sf /usr/share/git-core/contrib/hooks/pre-auto-gc-battery \
+# hooks/pre-auto-gc
if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1)
then
@@ -48,11 +40,6 @@
grep -q "drawing from 'AC Power'"
then
exit 0
-elif test -d /sys/bus/acpi/drivers/battery && test 0 = \
- "$(find /sys/bus/acpi/drivers/battery/ -type l | wc -l)";
-then
- # No battery exists.
- exit 0
fi
echo "Auto packing deferred; not on AC"
diff --git a/man/repo-gitc-delete.1 b/man/repo-gitc-delete.1
deleted file mode 100644
index 6158bd3..0000000
--- a/man/repo-gitc-delete.1
+++ /dev/null
@@ -1,44 +0,0 @@
-.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
-.TH REPO "1" "July 2022" "repo gitc-delete" "Repo Manual"
-.SH NAME
-repo \- repo gitc-delete - manual page for repo gitc-delete
-.SH SYNOPSIS
-.B repo
-\fI\,gitc-delete\/\fR
-.SH DESCRIPTION
-Summary
-.PP
-Delete a GITC Client.
-.SH OPTIONS
-.TP
-\fB\-h\fR, \fB\-\-help\fR
-show this help message and exit
-.TP
-\fB\-f\fR, \fB\-\-force\fR
-force the deletion (no prompt)
-.SS Logging options:
-.TP
-\fB\-v\fR, \fB\-\-verbose\fR
-show all output
-.TP
-\fB\-q\fR, \fB\-\-quiet\fR
-only show errors
-.SS Multi\-manifest options:
-.TP
-\fB\-\-outer\-manifest\fR
-operate starting at the outermost manifest
-.TP
-\fB\-\-no\-outer\-manifest\fR
-do not operate on outer manifests
-.TP
-\fB\-\-this\-manifest\-only\fR
-only operate on this (sub)manifest
-.TP
-\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
-operate on this manifest and its submanifests
-.PP
-Run `repo help gitc\-delete` to view the detailed manual.
-.SH DETAILS
-.PP
-This subcommand deletes the current GITC client, deleting the GITC manifest and
-all locally downloaded sources.
diff --git a/man/repo-gitc-init.1 b/man/repo-gitc-init.1
deleted file mode 100644
index 02581c6..0000000
--- a/man/repo-gitc-init.1
+++ /dev/null
@@ -1,175 +0,0 @@
-.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
-.TH REPO "1" "October 2022" "repo gitc-init" "Repo Manual"
-.SH NAME
-repo \- repo gitc-init - manual page for repo gitc-init
-.SH SYNOPSIS
-.B repo
-\fI\,gitc-init \/\fR[\fI\,options\/\fR] [\fI\,client name\/\fR]
-.SH DESCRIPTION
-Summary
-.PP
-Initialize a GITC Client.
-.SH OPTIONS
-.TP
-\fB\-h\fR, \fB\-\-help\fR
-show this help message and exit
-.SS Logging options:
-.TP
-\fB\-v\fR, \fB\-\-verbose\fR
-show all output
-.TP
-\fB\-q\fR, \fB\-\-quiet\fR
-only show errors
-.SS Manifest options:
-.TP
-\fB\-u\fR URL, \fB\-\-manifest\-url\fR=\fI\,URL\/\fR
-manifest repository location
-.TP
-\fB\-b\fR REVISION, \fB\-\-manifest\-branch\fR=\fI\,REVISION\/\fR
-manifest branch or revision (use HEAD for default)
-.TP
-\fB\-m\fR NAME.xml, \fB\-\-manifest\-name\fR=\fI\,NAME\/\fR.xml
-initial manifest file
-.TP
-\fB\-g\fR GROUP, \fB\-\-groups\fR=\fI\,GROUP\/\fR
-restrict manifest projects to ones with specified
-group(s) [default|all|G1,G2,G3|G4,\-G5,\-G6]
-.TP
-\fB\-p\fR PLATFORM, \fB\-\-platform\fR=\fI\,PLATFORM\/\fR
-restrict manifest projects to ones with a specified
-platform group [auto|all|none|linux|darwin|...]
-.TP
-\fB\-\-submodules\fR
-sync any submodules associated with the manifest repo
-.TP
-\fB\-\-standalone\-manifest\fR
-download the manifest as a static file rather then
-create a git checkout of the manifest repo
-.TP
-\fB\-\-manifest\-depth\fR=\fI\,DEPTH\/\fR
-create a shallow clone of the manifest repo with given
-depth (0 for full clone); see git clone (default: 0)
-.SS Manifest (only) checkout options:
-.TP
-\fB\-\-current\-branch\fR
-fetch only current manifest branch from server
-(default)
-.TP
-\fB\-\-no\-current\-branch\fR
-fetch all manifest branches from server
-.TP
-\fB\-\-tags\fR
-fetch tags in the manifest
-.TP
-\fB\-\-no\-tags\fR
-don't fetch tags in the manifest
-.SS Checkout modes:
-.TP
-\fB\-\-mirror\fR
-create a replica of the remote repositories rather
-than a client working directory
-.TP
-\fB\-\-archive\fR
-checkout an archive instead of a git repository for
-each project. See git archive.
-.TP
-\fB\-\-worktree\fR
-use git\-worktree to manage projects
-.SS Project checkout optimizations:
-.TP
-\fB\-\-reference\fR=\fI\,DIR\/\fR
-location of mirror directory
-.TP
-\fB\-\-dissociate\fR
-dissociate from reference mirrors after clone
-.TP
-\fB\-\-depth\fR=\fI\,DEPTH\/\fR
-create a shallow clone with given depth; see git clone
-.TP
-\fB\-\-partial\-clone\fR
-perform partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
-.TP
-\fB\-\-no\-partial\-clone\fR
-disable use of partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
-.TP
-\fB\-\-partial\-clone\-exclude\fR=\fI\,PARTIAL_CLONE_EXCLUDE\/\fR
-exclude the specified projects (a comma\-delimited
-project names) from partial clone (https://gitscm.com/docs/gitrepositorylayout#_code_partialclone_code)
-.TP
-\fB\-\-clone\-filter\fR=\fI\,CLONE_FILTER\/\fR
-filter for use with \fB\-\-partial\-clone\fR [default:
-blob:none]
-.TP
-\fB\-\-use\-superproject\fR
-use the manifest superproject to sync projects;
-implies \fB\-c\fR
-.TP
-\fB\-\-no\-use\-superproject\fR
-disable use of manifest superprojects
-.TP
-\fB\-\-clone\-bundle\fR
-enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
-not \fB\-\-partial\-clone\fR)
-.TP
-\fB\-\-no\-clone\-bundle\fR
-disable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if
-\fB\-\-partial\-clone\fR)
-.TP
-\fB\-\-git\-lfs\fR
-enable Git LFS support
-.TP
-\fB\-\-no\-git\-lfs\fR
-disable Git LFS support
-.SS repo Version options:
-.TP
-\fB\-\-repo\-url\fR=\fI\,URL\/\fR
-repo repository location ($REPO_URL)
-.TP
-\fB\-\-repo\-rev\fR=\fI\,REV\/\fR
-repo branch or revision ($REPO_REV)
-.TP
-\fB\-\-no\-repo\-verify\fR
-do not verify repo source code
-.SS Other options:
-.TP
-\fB\-\-config\-name\fR
-Always prompt for name/e\-mail
-.SS GITC options:
-.TP
-\fB\-f\fR MANIFEST_FILE, \fB\-\-manifest\-file\fR=\fI\,MANIFEST_FILE\/\fR
-Optional manifest file to use for this GITC client.
-.TP
-\fB\-c\fR GITC_CLIENT, \fB\-\-gitc\-client\fR=\fI\,GITC_CLIENT\/\fR
-Name of the gitc_client instance to create or modify.
-.SS Multi\-manifest:
-.TP
-\fB\-\-outer\-manifest\fR
-operate starting at the outermost manifest
-.TP
-\fB\-\-no\-outer\-manifest\fR
-do not operate on outer manifests
-.TP
-\fB\-\-this\-manifest\-only\fR
-only operate on this (sub)manifest
-.TP
-\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
-operate on this manifest and its submanifests
-.PP
-Run `repo help gitc\-init` to view the detailed manual.
-.SH DETAILS
-.PP
-The 'repo gitc\-init' command is ran to initialize a new GITC client for use with
-the GITC file system.
-.PP
-This command will setup the client directory, initialize repo, just like repo
-init does, and then downloads the manifest collection and installs it in the
-\&.repo/directory of the GITC client.
-.PP
-Once this is done, a GITC manifest is generated by pulling the HEAD SHA for each
-project and generates the properly formatted XML file and installs it as
-\&.manifest in the GITC client directory.
-.PP
-The \fB\-c\fR argument is required to specify the GITC client name.
-.PP
-The optional \fB\-f\fR argument can be used to specify the manifest file to use for
-this GITC client.
diff --git a/man/repo-manifest.1 b/man/repo-manifest.1
index e49836c..10ec2e7 100644
--- a/man/repo-manifest.1
+++ b/man/repo-manifest.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
-.TH REPO "1" "October 2022" "repo manifest" "Repo Manual"
+.TH REPO "1" "April 2024" "repo manifest" "Repo Manual"
.SH NAME
repo \- repo manifest - manual page for repo manifest
.SH SYNOPSIS
@@ -194,8 +194,9 @@
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
.IP
<!ELEMENT remove\-project EMPTY>
-<!ATTLIST remove\-project name CDATA #REQUIRED>
-<!ATTLIST remove\-project optional CDATA #IMPLIED>
+<!ATTLIST remove\-project name CDATA #IMPLIED>
+<!ATTLIST remove\-project path CDATA #IMPLIED>
+<!ATTLIST remove\-project optional CDATA #IMPLIED>
.IP
<!ELEMENT repo\-hooks EMPTY>
<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
@@ -210,8 +211,9 @@
<!ATTLIST contactinfo bugurl CDATA #REQUIRED>
.IP
<!ELEMENT include EMPTY>
-<!ATTLIST include name CDATA #REQUIRED>
-<!ATTLIST include groups CDATA #IMPLIED>
+<!ATTLIST include name CDATA #REQUIRED>
+<!ATTLIST include groups CDATA #IMPLIED>
+<!ATTLIST include revision CDATA #IMPLIED>
.PP
]>
```
@@ -533,13 +535,24 @@
.PP
Element remove\-project
.PP
-Deletes the named project from the internal manifest table, possibly allowing a
+Deletes a project from the internal manifest table, possibly allowing a
subsequent project element in the same manifest file to replace the project with
a different source.
.PP
This element is mostly useful in a local manifest file, where the user can
remove a project, and possibly replace it with their own definition.
.PP
+The project `name` or project `path` can be used to specify the remove target
+meaning one of them is required. If only name is specified, all projects with
+that name are removed.
+.PP
+If both name and path are specified, only projects with the same name and path
+are removed, meaning projects with the same name but in other locations are
+kept.
+.PP
+If only path is specified, a matching project is removed regardless of its name.
+Logic otherwise behaves like both are specified.
+.PP
Attribute `optional`: Set to true to ignore remove\-project elements with no
matching `project` element.
.PP
@@ -608,7 +621,10 @@
included manifests carry all parent include groups. Same syntax as the
corresponding element of `project`.
.PP
-Local Manifests
+Attribute `revision`: Name of a Git branch (e.g. `main` or `refs/heads/main`)
+default to which all projects in the included manifest belong.
+.PP
+Local Manifests
.PP
Additional remotes and projects may be added through local manifest files stored
in `$TOP_DIR/.repo/local_manifests/*.xml`.
diff --git a/man/repo-smartsync.1 b/man/repo-smartsync.1
index c1abbb3..1e97910 100644
--- a/man/repo-smartsync.1
+++ b/man/repo-smartsync.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
-.TH REPO "1" "November 2022" "repo smartsync" "Repo Manual"
+.TH REPO "1" "April 2024" "repo smartsync" "Repo Manual"
.SH NAME
repo \- repo smartsync - manual page for repo smartsync
.SH SYNOPSIS
@@ -37,6 +37,11 @@
point to a different object directory. WARNING: this
may cause loss of data
.TP
+\fB\-\-force\-checkout\fR
+force checkout even if it results in throwing away
+uncommitted modifications. WARNING: this may cause
+loss of data
+.TP
\fB\-\-force\-remove\-dirty\fR
force remove projects with uncommitted modifications
if projects no longer exist in the manifest. WARNING:
diff --git a/man/repo-sync.1 b/man/repo-sync.1
index f006c03..0327942 100644
--- a/man/repo-sync.1
+++ b/man/repo-sync.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
-.TH REPO "1" "November 2022" "repo sync" "Repo Manual"
+.TH REPO "1" "April 2024" "repo sync" "Repo Manual"
.SH NAME
repo \- repo sync - manual page for repo sync
.SH SYNOPSIS
@@ -37,6 +37,11 @@
point to a different object directory. WARNING: this
may cause loss of data
.TP
+\fB\-\-force\-checkout\fR
+force checkout even if it results in throwing away
+uncommitted modifications. WARNING: this may cause
+loss of data
+.TP
\fB\-\-force\-remove\-dirty\fR
force remove projects with uncommitted modifications
if projects no longer exist in the manifest. WARNING:
@@ -185,6 +190,11 @@
they have previously been linked to a different object directory. WARNING: This
may cause data to be lost since refs may be removed when overwriting.
.PP
+The \fB\-\-force\-checkout\fR option can be used to force git to switch revs even if the
+index or the working tree differs from HEAD, and if there are untracked files.
+WARNING: This may cause data to be lost since uncommitted changes may be
+removed.
+.PP
The \fB\-\-force\-remove\-dirty\fR option can be used to remove previously used projects
with uncommitted changes. WARNING: This may cause data to be lost since
uncommitted changes may be removed with projects that no longer exist in the
diff --git a/man/repo-upload.1 b/man/repo-upload.1
index b8f6677..c0ae581 100644
--- a/man/repo-upload.1
+++ b/man/repo-upload.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
-.TH REPO "1" "August 2022" "repo upload" "Repo Manual"
+.TH REPO "1" "June 2024" "repo upload" "Repo Manual"
.SH NAME
repo \- repo upload - manual page for repo upload
.SH SYNOPSIS
@@ -18,8 +18,11 @@
number of jobs to run in parallel (default: based on
number of CPU cores)
.TP
-\fB\-t\fR
-send local branch name to Gerrit Code Review
+\fB\-t\fR, \fB\-\-topic\-branch\fR
+set the topic to the local branch name
+.TP
+\fB\-\-topic\fR=\fI\,TOPIC\/\fR
+set topic for the change
.TP
\fB\-\-hashtag\fR=\fI\,HASHTAGS\/\fR, \fB\-\-ht\fR=\fI\,HASHTAGS\/\fR
add hashtags (comma delimited) to the review
@@ -30,6 +33,9 @@
\fB\-l\fR LABELS, \fB\-\-label\fR=\fI\,LABELS\/\fR
add a label when uploading
.TP
+\fB\-\-pd\fR=\fI\,PATCHSET_DESCRIPTION\/\fR, \fB\-\-patchset\-description\fR=\fI\,PATCHSET_DESCRIPTION\/\fR
+description for patchset
+.TP
\fB\-\-re\fR=\fI\,REVIEWERS\/\fR, \fB\-\-reviewers\fR=\fI\,REVIEWERS\/\fR
request reviews from these people
.TP
@@ -198,6 +204,12 @@
Control e\-mail notifications when uploading.
https://gerrit\-review.googlesource.com/Documentation/user\-upload.html#notify
.PP
+review.URL.uploadwarningthreshold:
+.PP
+Repo will warn you if you are attempting to upload a large number of commits in
+one or more branches. By default, the threshold is five commits. This option
+allows you to override the warning threshold to a different value.
+.PP
References
.PP
Gerrit Code Review: https://www.gerritcodereview.com/
diff --git a/man/repo.1 b/man/repo.1
index 46425c6..bda68c3 100644
--- a/man/repo.1
+++ b/man/repo.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
-.TH REPO "1" "June 2023" "repo" "Repo Manual"
+.TH REPO "1" "April 2024" "repo" "Repo Manual"
.SH NAME
repo \- repository management tool built on top of git
.SH SYNOPSIS
@@ -79,12 +79,6 @@
forall
Run a shell command in each project
.TP
-gitc\-delete
-Delete a GITC Client.
-.TP
-gitc\-init
-Initialize a GITC Client.
-.TP
grep
Print lines matching a pattern
.TP
diff --git a/manifest_xml.py b/manifest_xml.py
index 4f75212..b26b0ca 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -435,11 +435,6 @@
self.parent_groups = parent_groups
self.default_groups = default_groups
- if outer_client and self.isGitcClient:
- raise ManifestParseError(
- "Multi-manifest is incompatible with `gitc-init`"
- )
-
if submanifest_path and not outer_client:
# If passing a submanifest_path, there must be an outer_client.
raise ManifestParseError(f"Bad call to {self.__class__.__name__}")
@@ -2290,7 +2285,6 @@
submanifest_path: The submanifest root relative to the repo root.
**kwargs: Additional keyword arguments, passed to XmlManifest.
"""
- self.isGitcClient = False
submanifest_path = submanifest_path or ""
if submanifest_path:
self._CheckLocalPath(submanifest_path)
diff --git a/platform_utils.py b/platform_utils.py
index 4cf994b..e20198e 100644
--- a/platform_utils.py
+++ b/platform_utils.py
@@ -251,32 +251,3 @@
return platform_utils_win32.readlink(_makelongpath(path))
else:
return os.readlink(path)
-
-
-def realpath(path):
- """Return the canonical path of the specified filename, eliminating
- any symbolic links encountered in the path.
-
- Availability: Windows, Unix.
- """
- if isWindows():
- current_path = os.path.abspath(path)
- path_tail = []
- for c in range(0, 100): # Avoid cycles
- if islink(current_path):
- target = readlink(current_path)
- current_path = os.path.join(
- os.path.dirname(current_path), target
- )
- else:
- basename = os.path.basename(current_path)
- if basename == "":
- path_tail.append(current_path)
- break
- path_tail.append(basename)
- current_path = os.path.dirname(current_path)
- path_tail.reverse()
- result = os.path.normpath(os.path.join(*path_tail))
- return result
- else:
- return os.path.realpath(path)
diff --git a/project.py b/project.py
index 9563e7d..4aeee62 100644
--- a/project.py
+++ b/project.py
@@ -148,7 +148,7 @@
"""
global _project_hook_list
if _project_hook_list is None:
- d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
+ d = os.path.realpath(os.path.abspath(os.path.dirname(__file__)))
d = os.path.join(d, "hooks")
_project_hook_list = [
os.path.join(d, x) for x in platform_utils.listdir(d)
@@ -260,7 +260,7 @@
self,
people,
dryrun=False,
- auto_topic=False,
+ topic=None,
hashtags=(),
labels=(),
private=False,
@@ -276,7 +276,7 @@
branch=self.name,
people=people,
dryrun=dryrun,
- auto_topic=auto_topic,
+ topic=topic,
hashtags=hashtags,
labels=labels,
private=private,
@@ -730,12 +730,34 @@
return None
def IsRebaseInProgress(self):
+ """Returns true if a rebase or "am" is in progress"""
+ # "rebase-apply" is used for "git rebase".
+ # "rebase-merge" is used for "git am".
return (
os.path.exists(self.work_git.GetDotgitPath("rebase-apply"))
or os.path.exists(self.work_git.GetDotgitPath("rebase-merge"))
or os.path.exists(os.path.join(self.worktree, ".dotest"))
)
+ def IsCherryPickInProgress(self):
+ """Returns True if a cherry-pick is in progress."""
+ return os.path.exists(self.work_git.GetDotgitPath("CHERRY_PICK_HEAD"))
+
+ def _AbortRebase(self):
+ """Abort ongoing rebase, cherry-pick or patch apply (am).
+
+ If no rebase, cherry-pick or patch apply was in progress, this method
+ ignores the status and continues.
+ """
+
+ def _git(*args):
+ # Ignore return code, in case there was no rebase in progress.
+ GitCommand(self, *args, log_as_error=False).Wait()
+
+ _git("cherry-pick", "--abort")
+ _git("rebase", "--abort")
+ _git("am", "--abort")
+
def IsDirty(self, consider_untracked=True):
"""Is the working directory modified in some way?"""
self.work_git.update_index(
@@ -1085,7 +1107,7 @@
branch=None,
people=([], []),
dryrun=False,
- auto_topic=False,
+ topic=None,
hashtags=(),
labels=(),
private=False,
@@ -1148,8 +1170,7 @@
# This stops git from pushing all reachable annotated tags when
# push.followTags is configured. Gerrit does not accept any tags
# pushed to a CL.
- if git_require((1, 8, 3)):
- cmd.append("--no-follow-tags")
+ cmd.append("--no-follow-tags")
for push_option in push_options or []:
cmd.append("-o")
@@ -1162,8 +1183,8 @@
ref_spec = f"{R_HEADS + branch.name}:refs/for/{dest_branch}"
opts = []
- if auto_topic:
- opts += ["topic=" + branch.name]
+ if topic is not None:
+ opts += [f"topic={topic}"]
opts += ["t=%s" % p for p in hashtags]
# NB: No need to encode labels as they've been validated above.
opts += ["l=%s" % p for p in labels]
@@ -1558,8 +1579,6 @@
self._InitHooks()
def _CopyAndLinkFiles(self):
- if self.client.isGitcClient:
- return
for copyfile in self.copyfiles:
copyfile._Copy()
for linkfile in self.linkfiles:
@@ -1682,7 +1701,15 @@
if branch is None or syncbuf.detach_head:
# Currently on a detached HEAD. The user is assumed to
# not have any local modifications worth worrying about.
- if self.IsRebaseInProgress():
+ rebase_in_progress = (
+ self.IsRebaseInProgress() or self.IsCherryPickInProgress()
+ )
+ if rebase_in_progress and force_checkout:
+ self._AbortRebase()
+ rebase_in_progress = (
+ self.IsRebaseInProgress() or self.IsCherryPickInProgress()
+ )
+ if rebase_in_progress:
fail(_PriorSyncFailedError(project=self.name))
return
@@ -1922,7 +1949,7 @@
# remove because it will recursively delete projects -- we handle that
# ourselves below. https://crbug.com/git/48
if self.use_git_worktrees:
- needle = platform_utils.realpath(self.gitdir)
+ needle = os.path.realpath(self.gitdir)
# Find the git worktree commondir under .repo/worktrees/.
output = self.bare_git.worktree("list", "--porcelain").splitlines()[
0
@@ -1936,7 +1963,7 @@
with open(gitdir) as fp:
relpath = fp.read().strip()
# Resolve the checkout path and see if it matches this project.
- fullpath = platform_utils.realpath(
+ fullpath = os.path.realpath(
os.path.join(configs, name, relpath)
)
if fullpath == needle:
@@ -2663,12 +2690,7 @@
branch = None
else:
branch = self.revisionExpr
- if (
- not self.manifest.IsMirror
- and is_sha1
- and depth
- and git_require((1, 8, 3))
- ):
+ if not self.manifest.IsMirror and is_sha1 and depth:
# Shallow checkout of a specific commit, fetch from that commit and
# not the heads only as the commit might be deeper in the history.
spec.append(branch)
@@ -2880,6 +2902,8 @@
def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
platform_utils.remove(dstPath, missing_ok=True)
+ # We do not use curl's --retry option since it generally doesn't
+ # actually retry anything; code 18 for example, it will not retry on.
cmd = ["curl", "--fail", "--output", tmpPath, "--netrc", "--location"]
if quiet:
cmd += ["--silent", "--show-error"]
@@ -2916,11 +2940,18 @@
(output, _) = proc.communicate()
curlret = proc.returncode
- if curlret == 22:
+ if curlret in (22, 35, 56, 92):
+ # We use --fail so curl exits with unique status.
# From curl man page:
- # 22: HTTP page not retrieved. The requested url was not found
- # or returned another error with the HTTP error code being 400
- # or above. This return code only appears if -f, --fail is used.
+ # 22: HTTP page not retrieved. The requested url was not found
+ # or returned another error with the HTTP error code being
+ # 400 or above.
+ # 35: SSL connect error. The SSL handshaking failed. This can
+ # be thrown by Google storage sometimes.
+ # 56: Failure in receiving network data. This shows up with
+ # HTTP/404 on Google storage.
+ # 92: Stream error in HTTP/2 framing layer. Basically the same
+ # as 22 -- Google storage sometimes throws 500's.
if verbose:
print(
"%s: Unable to retrieve clone.bundle; ignoring."
@@ -3071,14 +3102,12 @@
"Retrying clone after deleting %s", self.gitdir
)
try:
- platform_utils.rmtree(
- platform_utils.realpath(self.gitdir)
- )
+ platform_utils.rmtree(os.path.realpath(self.gitdir))
if self.worktree and os.path.exists(
- platform_utils.realpath(self.worktree)
+ os.path.realpath(self.worktree)
):
platform_utils.rmtree(
- platform_utils.realpath(self.worktree)
+ os.path.realpath(self.worktree)
)
return self._InitGitDir(
mirror_git=mirror_git,
@@ -3164,7 +3193,7 @@
self._InitHooks(quiet=quiet)
def _InitHooks(self, quiet=False):
- hooks = platform_utils.realpath(os.path.join(self.objdir, "hooks"))
+ hooks = os.path.realpath(os.path.join(self.objdir, "hooks"))
if not os.path.exists(hooks):
os.makedirs(hooks)
@@ -3307,9 +3336,9 @@
dst_path = os.path.join(destdir, name)
src_path = os.path.join(srcdir, name)
- dst = platform_utils.realpath(dst_path)
+ dst = os.path.realpath(dst_path)
if os.path.lexists(dst):
- src = platform_utils.realpath(src_path)
+ src = os.path.realpath(src_path)
# Fail if the links are pointing to the wrong place.
if src != dst:
logger.error(
@@ -3345,10 +3374,10 @@
if copy_all:
to_copy = platform_utils.listdir(gitdir)
- dotgit = platform_utils.realpath(dotgit)
+ dotgit = os.path.realpath(dotgit)
for name in set(to_copy).union(to_symlink):
try:
- src = platform_utils.realpath(os.path.join(gitdir, name))
+ src = os.path.realpath(os.path.join(gitdir, name))
dst = os.path.join(dotgit, name)
if os.path.lexists(dst):
@@ -3445,9 +3474,7 @@
else:
if not init_dotgit:
# See if the project has changed.
- if platform_utils.realpath(
- self.gitdir
- ) != platform_utils.realpath(dotgit):
+ if os.path.realpath(self.gitdir) != os.path.realpath(dotgit):
platform_utils.remove(dotgit)
if init_dotgit or not os.path.exists(dotgit):
diff --git a/release/update-hooks b/release/update-hooks
new file mode 100755
index 0000000..def4bba
--- /dev/null
+++ b/release/update-hooks
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Helper tool for updating hooks from their various upstreams."""
+
+import argparse
+import base64
+import json
+from pathlib import Path
+import sys
+from typing import List, Optional
+import urllib.request
+
+
+assert sys.version_info >= (3, 8), "Python 3.8+ required"
+
+
+TOPDIR = Path(__file__).resolve().parent.parent
+HOOKS_DIR = TOPDIR / "hooks"
+
+
+def update_hook_commit_msg() -> None:
+ """Update commit-msg hook from Gerrit."""
+ hook = HOOKS_DIR / "commit-msg"
+ print(
+ f"{hook.name}: Updating from https://gerrit.googlesource.com/gerrit/"
+ "+/HEAD/resources/com/google/gerrit/server/tools/root/hooks/commit-msg"
+ )
+
+ # Get the current commit.
+ url = "https://gerrit.googlesource.com/gerrit/+/HEAD?format=JSON"
+ with urllib.request.urlopen(url) as fp:
+ data = fp.read()
+ # Discard the xss protection.
+ data = data.split(b"\n", 1)[1]
+ data = json.loads(data)
+ commit = data["commit"]
+
+ # Fetch the data for that commit.
+ url = (
+ f"https://gerrit.googlesource.com/gerrit/+/{commit}/"
+ "resources/com/google/gerrit/server/tools/root/hooks/commit-msg"
+ )
+ with urllib.request.urlopen(f"{url}?format=TEXT") as fp:
+ data = fp.read()
+
+ # gitiles base64 encodes text data.
+ data = base64.b64decode(data)
+
+ # Inject header into the hook.
+ lines = data.split(b"\n")
+ lines = (
+ lines[:1]
+ + [
+ b"# DO NOT EDIT THIS FILE",
+ (
+ b"# All updates should be sent upstream: "
+ b"https://gerrit.googlesource.com/gerrit/"
+ ),
+ f"# This is synced from commit: {commit}".encode("utf-8"),
+ b"# DO NOT EDIT THIS FILE",
+ ]
+ + lines[1:]
+ )
+ data = b"\n".join(lines)
+
+ # Update the hook.
+ hook.write_bytes(data)
+ hook.chmod(0o755)
+
+
+def update_hook_pre_auto_gc() -> None:
+ """Update pre-auto-gc hook from git."""
+ hook = HOOKS_DIR / "pre-auto-gc"
+ print(
+ f"{hook.name}: Updating from https://github.com/git/git/"
+ "HEAD/contrib/hooks/pre-auto-gc-battery"
+ )
+
+ # Get the current commit.
+ headers = {
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28",
+ }
+ url = "https://api.github.com/repos/git/git/git/refs/heads/master"
+ req = urllib.request.Request(url, headers=headers)
+ with urllib.request.urlopen(req) as fp:
+ data = fp.read()
+ data = json.loads(data)
+
+ # Fetch the data for that commit.
+ commit = data["object"]["sha"]
+ url = (
+ f"https://raw.githubusercontent.com/git/git/{commit}/"
+ "contrib/hooks/pre-auto-gc-battery"
+ )
+ with urllib.request.urlopen(url) as fp:
+ data = fp.read()
+
+ # Inject header into the hook.
+ lines = data.split(b"\n")
+ lines = (
+ lines[:1]
+ + [
+ b"# DO NOT EDIT THIS FILE",
+ (
+ b"# All updates should be sent upstream: "
+ b"https://github.com/git/git/"
+ ),
+ f"# This is synced from commit: {commit}".encode("utf-8"),
+ b"# DO NOT EDIT THIS FILE",
+ ]
+ + lines[1:]
+ )
+ data = b"\n".join(lines)
+
+ # Update the hook.
+ hook.write_bytes(data)
+ hook.chmod(0o755)
+
+
+def main(argv: Optional[List[str]] = None) -> Optional[int]:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.parse_args(argv)
+
+ update_hook_commit_msg()
+ update_hook_pre_auto_gc()
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))
diff --git a/repo b/repo
index 98fd5f7..b2114e5 100755
--- a/repo
+++ b/repo
@@ -210,7 +210,6 @@
# NB: The version of git that the repo launcher requires may be much older than
# the version of git that the main repo source tree requires. Keeping this at
# an older version also makes it easier for users to upgrade/rollback as needed.
-# See requirements.json for versions.
MIN_GIT_VERSION = (1, 7, 9) # minimum supported git version
repodir = ".repo" # name of repo's private directory
S_repo = "repo" # special repo repository
@@ -1237,13 +1236,13 @@
return cls(json_data)
- def _get_soft_ver(self, pkg):
+ def get_soft_ver(self, pkg):
"""Return the soft version for |pkg| if it exists."""
- return self.requirements.get(pkg, {}).get("soft", ())
+ return tuple(self.requirements.get(pkg, {}).get("soft", ()))
- def _get_hard_ver(self, pkg):
+ def get_hard_ver(self, pkg):
"""Return the hard version for |pkg| if it exists."""
- return self.requirements.get(pkg, {}).get("hard", ())
+ return tuple(self.requirements.get(pkg, {}).get("hard", ()))
@staticmethod
def _format_ver(ver):
@@ -1253,8 +1252,8 @@
def assert_ver(self, pkg, curr_ver):
"""Verify |pkg|'s |curr_ver| is new enough."""
curr_ver = tuple(curr_ver)
- soft_ver = tuple(self._get_soft_ver(pkg))
- hard_ver = tuple(self._get_hard_ver(pkg))
+ soft_ver = tuple(self.get_soft_ver(pkg))
+ hard_ver = tuple(self.get_hard_ver(pkg))
if curr_ver < hard_ver:
print(
f'repo: error: Your version of "{pkg}" '
diff --git a/repo_logging.py b/repo_logging.py
index 20a5342..639382a 100644
--- a/repo_logging.py
+++ b/repo_logging.py
@@ -39,8 +39,8 @@
def __init__(self, config):
super().__init__(config, "logs")
- self.error = self.colorer("error", fg="red")
- self.warning = self.colorer("warn", fg="yellow")
+ self.error = self.nofmt_colorer("error", fg="red")
+ self.warning = self.nofmt_colorer("warn", fg="yellow")
self.levelMap = {
"WARNING": self.warning,
"ERROR": self.error,
diff --git a/requirements.json b/requirements.json
index dac9a4f..2976eec 100644
--- a/requirements.json
+++ b/requirements.json
@@ -46,8 +46,6 @@
# Supported git versions.
#
- # git-1.7.9 is in Ubuntu Precise.
- # git-1.7.10 is in Debian Wheezy.
# git-1.9.1 is in Ubuntu Trusty.
# git-2.1.4 is in Debian Jessie.
# git-2.7.4 is in Ubuntu Xenial.
@@ -55,7 +53,7 @@
# git-2.17.0 is in Ubuntu Bionic.
# git-2.20.1 is in Debian Buster.
"git": {
- "hard": [1, 7, 9],
+ "hard": [1, 9, 1],
"soft": [2, 7, 4]
}
}
diff --git a/run_tests b/run_tests
index 7307f82..3e0e501 100755
--- a/run_tests
+++ b/run_tests
@@ -32,6 +32,7 @@
extra_programs = [
"repo",
"run_tests",
+ "release/update-hooks",
"release/update-manpages",
]
return subprocess.run(
diff --git a/ssh.py b/ssh.py
index 54a7730..ffa0d6c 100644
--- a/ssh.py
+++ b/ssh.py
@@ -24,6 +24,7 @@
import tempfile
import time
+from git_command import git
import platform_utils
from repo_trace import Trace
@@ -211,7 +212,33 @@
# and print to the log there.
pass
- command = command_base[:1] + ["-M", "-N"] + command_base[1:]
+ # Git protocol V2 is a new feature in git 2.18.0, made default in
+ # git 2.26.0
+ # It is faster and more efficient than V1.
+ # To enable it when using SSH, the environment variable GIT_PROTOCOL
+ # must be set in the SSH side channel when establishing the connection
+ # to the git server.
+ # See https://git-scm.com/docs/protocol-v2#_ssh_and_file_transport
+ # Normally git does this by itself. But here, where the SSH connection
+ # is established manually over ControlMaster via the repo-tool, it must
+ # be passed in explicitly instead.
+ # Based on https://git-scm.com/docs/gitprotocol-pack#_extra_parameters,
+ # GIT_PROTOCOL is considered an "Extra Parameter" and must be ignored
+ # by servers that do not understand it. This means that it is safe to
+ # set it even when connecting to older servers.
+ # It should also be safe to set the environment variable for older
+ # local git versions, since it is only part of the ssh side channel.
+ git_protocol_version = _get_git_protocol_version()
+ ssh_git_protocol_args = [
+ "-o",
+ f"SetEnv GIT_PROTOCOL=version={git_protocol_version}",
+ ]
+
+ command = (
+ command_base[:1]
+ + ["-M", "-N", *ssh_git_protocol_args]
+ + command_base[1:]
+ )
p = None
try:
with Trace("Call to ssh: %s", " ".join(command)):
@@ -293,3 +320,32 @@
tempfile.mkdtemp("", "ssh-", tmp_dir), "master-" + tokens
)
return self._sock_path
+
+
+@functools.lru_cache(maxsize=1)
+def _get_git_protocol_version() -> str:
+ """Return the git protocol version.
+
+ The version is found by first reading the global git config.
+ If no git config for protocol version exists, try to deduce the default
+ protocol version based on the git version.
+
+ See https://git-scm.com/docs/gitprotocol-v2 for details.
+ """
+ try:
+ return subprocess.check_output(
+ ["git", "config", "--get", "--global", "protocol.version"],
+ encoding="utf-8",
+ stderr=subprocess.PIPE,
+ ).strip()
+ except subprocess.CalledProcessError as e:
+ if e.returncode == 1:
+ # Exit code 1 means that the git config key was not found.
+ # Try to imitate the defaults that git would have used.
+ git_version = git.version_tuple()
+ if git_version >= (2, 26, 0):
+ # Since git version 2.26, protocol v2 is the default.
+ return "2"
+ return "1"
+ # Other exit codes indicate error with reading the config.
+ raise
diff --git a/subcmds/init.py b/subcmds/init.py
index e53d033..7617bc1 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -21,10 +21,9 @@
from error import RepoUnhandledExceptionError
from error import UpdateManifestError
from git_command import git_require
-from git_command import MIN_GIT_VERSION_HARD
-from git_command import MIN_GIT_VERSION_SOFT
from repo_logging import RepoLogger
from wrapper import Wrapper
+from wrapper import WrapperDir
logger = RepoLogger(__file__)
@@ -331,13 +330,17 @@
self.OptionParser.error("too many arguments to init")
def Execute(self, opt, args):
- git_require(MIN_GIT_VERSION_HARD, fail=True)
- if not git_require(MIN_GIT_VERSION_SOFT):
+ wrapper = Wrapper()
+
+ reqs = wrapper.Requirements.from_dir(WrapperDir())
+ git_require(reqs.get_hard_ver("git"), fail=True)
+ min_git_version_soft = reqs.get_soft_ver("git")
+ if not git_require(min_git_version_soft):
logger.warning(
"repo: warning: git-%s+ will soon be required; "
"please upgrade your version of git to maintain "
"support.",
- ".".join(str(x) for x in MIN_GIT_VERSION_SOFT),
+ ".".join(str(x) for x in min_git_version_soft),
)
rp = self.manifest.repoProject
@@ -350,7 +353,6 @@
# Handle new --repo-rev requests.
if opt.repo_rev:
- wrapper = Wrapper()
try:
remote_ref, rev = wrapper.check_repo_rev(
rp.worktree,
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 081f1af..8039a1c 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -218,9 +218,14 @@
def _Options(self, p):
p.add_option(
"-t",
+ "--topic-branch",
dest="auto_topic",
action="store_true",
- help="send local branch name to Gerrit Code Review",
+ help="set the topic to the local branch name",
+ )
+ p.add_option(
+ "--topic",
+ help="set topic for the change",
)
p.add_option(
"--hashtag",
@@ -549,42 +554,14 @@
people = copy.deepcopy(original_people)
self._AppendAutoList(branch, people)
- # Check if there are local changes that may have been forgotten.
- changes = branch.project.UncommitedFiles()
- if opt.ignore_untracked_files:
- untracked = set(branch.project.UntrackedFiles())
- changes = [x for x in changes if x not in untracked]
-
- if changes:
- key = "review.%s.autoupload" % branch.project.remote.review
- answer = branch.project.config.GetBoolean(key)
-
- # If they want to auto upload, let's not ask because it
- # could be automated.
- if answer is None:
- print()
- print(
- "Uncommitted changes in %s (did you forget to "
- "amend?):" % branch.project.name
- )
- print("\n".join(changes))
- print("Continue uploading? (y/N) ", end="", flush=True)
- if opt.yes:
- print("<--yes>")
- a = "yes"
- else:
- a = sys.stdin.readline().strip().lower()
- if a not in ("y", "yes", "t", "true", "on"):
- print("skipping upload", file=sys.stderr)
- branch.uploaded = False
- branch.error = "User aborted"
- return
-
# Check if topic branches should be sent to the server during
# upload.
- if opt.auto_topic is not True:
- key = "review.%s.uploadtopic" % branch.project.remote.review
- opt.auto_topic = branch.project.config.GetBoolean(key)
+ if opt.topic is None:
+ if opt.auto_topic is not True:
+ key = "review.%s.uploadtopic" % branch.project.remote.review
+ opt.auto_topic = branch.project.config.GetBoolean(key)
+ if opt.auto_topic:
+ opt.topic = branch.name
def _ExpandCommaList(value):
"""Split |value| up into comma delimited entries."""
@@ -651,7 +628,7 @@
branch.UploadForReview(
people,
dryrun=opt.dryrun,
- auto_topic=opt.auto_topic,
+ topic=opt.topic,
hashtags=hashtags,
labels=labels,
private=opt.private,
diff --git a/tests/test_repo_logging.py b/tests/test_repo_logging.py
index 0f6a335..e072039 100644
--- a/tests/test_repo_logging.py
+++ b/tests/test_repo_logging.py
@@ -13,9 +13,14 @@
# limitations under the License.
"""Unit test for repo_logging module."""
+
+import contextlib
+import io
+import logging
import unittest
from unittest import mock
+from color import SetDefaultColoring
from error import RepoExitError
from repo_logging import RepoLogger
@@ -62,3 +67,35 @@
mock.call("Repo command failed: %s", "RepoExitError"),
]
)
+
+ def test_log_with_format_string(self):
+ """Test different log levels with format strings."""
+
+ # Set color output to "always" for consistent test results.
+ # This ensures the logger's behavior is uniform across different
+ # environments and git configurations.
+ SetDefaultColoring("always")
+
+ # Regex pattern to match optional ANSI color codes.
+ # \033 - Escape character
+ # \[ - Opening square bracket
+ # [0-9;]* - Zero or more digits or semicolons
+ # m - Ending 'm' character
+ # ? - Makes the entire group optional
+ opt_color = r"(\033\[[0-9;]*m)?"
+
+ for level in (logging.INFO, logging.WARN, logging.ERROR):
+ name = logging.getLevelName(level)
+
+ with self.subTest(level=level, name=name):
+ output = io.StringIO()
+
+ with contextlib.redirect_stderr(output):
+ logger = RepoLogger(__name__)
+ logger.log(level, "%s", "100% pass")
+
+ self.assertRegex(
+ output.getvalue().strip(),
+ f"^{opt_color}100% pass{opt_color}$",
+ f"failed for level {name}",
+ )
diff --git a/wrapper.py b/wrapper.py
index d882336..5508224 100644
--- a/wrapper.py
+++ b/wrapper.py
@@ -18,8 +18,12 @@
import os
+def WrapperDir():
+ return os.path.dirname(__file__)
+
+
def WrapperPath():
- return os.path.join(os.path.dirname(__file__), "repo")
+ return os.path.join(WrapperDir(), "repo")
@functools.lru_cache(maxsize=None)