We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
poetry tries to reinstall from git when using annotated tags as references
- [x] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) Poetry version.
- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).


- **OS version and name**: Debian Buster
- **Poetry version**: 1.0.2
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: https://gist.github.com/jrmlhermitte/bbfda76d59b820560bf2ff4a1e57d42d

## Issue
This came from a discussion on issue #691 

If we re-run the gist included above, poetry will try re-installing the referred library, even though the git tag has not changed.

This was discovered on the issue mentioned above. Here is the reply from @bibz 
> Thanks for the MRE.

> I can reproduce your example now.
> You found a different bug than we had, due to how git handled signed annotated tags.

> To summarise:

>    The (annotated) tag v5.1.3 itself is a git object with the short sha 73f60e6
>    The tree pointed to by the tag has the short sha 4056bbb

> The lock file refers to the tag object, because it is what v5.1.3 resolves to directly.
 Since the checked-out tree has a different sha, poetry thinks it is not up-to-date (the references are      different) and then re-install it.

> I think we need a new issue for this.


Would it be possible to investigate this, and possibly change the behaviour to not re-install the package?

Thanks for the great work on this wonderful library, keep it up!

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `poetry/vcs/git.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 import re
3 import subprocess
4 
5 from collections import namedtuple
6 
7 from poetry.utils._compat import decode
8 
9 
10 pattern_formats = {
11     "protocol": r"\w+",
12     "user": r"[a-zA-Z0-9_.-]+",
13     "resource": r"[a-zA-Z0-9_.-]+",
14     "port": r"\d+",
15     "path": r"[\w\-/\\]+",
16     "name": r"[\w\-]+",
17     "rev": r"[^@#]+",
18 }
19 
20 PATTERNS = [
21     re.compile(
22         r"^(git\+)?"
23         r"(?P<protocol>https?|git|ssh|rsync|file)://"
24         r"(?:(?P<user>{user})@)?"
25         r"(?P<resource>{resource})?"
26         r"(:(?P<port>{port}))?"
27         r"(?P<pathname>[:/\\]({path}[/\\])?"
28         r"((?P<name>{name}?)(\.git|[/\\])?)?)"
29         r"([@#](?P<rev>{rev}))?"
30         r"$".format(
31             user=pattern_formats["user"],
32             resource=pattern_formats["resource"],
33             port=pattern_formats["port"],
34             path=pattern_formats["path"],
35             name=pattern_formats["name"],
36             rev=pattern_formats["rev"],
37         )
38     ),
39     re.compile(
40         r"(git\+)?"
41         r"((?P<protocol>{protocol})://)"
42         r"(?:(?P<user>{user})@)?"
43         r"(?P<resource>{resource}:?)"
44         r"(:(?P<port>{port}))?"
45         r"(?P<pathname>({path})"
46         r"(?P<name>{name})(\.git|/)?)"
47         r"([@#](?P<rev>{rev}))?"
48         r"$".format(
49             protocol=pattern_formats["protocol"],
50             user=pattern_formats["user"],
51             resource=pattern_formats["resource"],
52             port=pattern_formats["port"],
53             path=pattern_formats["path"],
54             name=pattern_formats["name"],
55             rev=pattern_formats["rev"],
56         )
57     ),
58     re.compile(
59         r"^(?:(?P<user>{user})@)?"
60         r"(?P<resource>{resource})"
61         r"(:(?P<port>{port}))?"
62         r"(?P<pathname>([:/]{path}/)"
63         r"(?P<name>{name})(\.git|/)?)"
64         r"([@#](?P<rev>{rev}))?"
65         r"$".format(
66             user=pattern_formats["user"],
67             resource=pattern_formats["resource"],
68             port=pattern_formats["port"],
69             path=pattern_formats["path"],
70             name=pattern_formats["name"],
71             rev=pattern_formats["rev"],
72         )
73     ),
74     re.compile(
75         r"((?P<user>{user})@)?"
76         r"(?P<resource>{resource})"
77         r"[:/]{{1,2}}"
78         r"(?P<pathname>({path})"
79         r"(?P<name>{name})(\.git|/)?)"
80         r"([@#](?P<rev>{rev}))?"
81         r"$".format(
82             user=pattern_formats["user"],
83             resource=pattern_formats["resource"],
84             path=pattern_formats["path"],
85             name=pattern_formats["name"],
86             rev=pattern_formats["rev"],
87         )
88     ),
89 ]
90 
91 
92 class ParsedUrl:
93     def __init__(self, protocol, resource, pathname, user, port, name, rev):
94         self.protocol = protocol
95         self.resource = resource
96         self.pathname = pathname
97         self.user = user
98         self.port = port
99         self.name = name
100         self.rev = rev
101 
102     @classmethod
103     def parse(cls, url):  # type: () -> ParsedUrl
104         for pattern in PATTERNS:
105             m = pattern.match(url)
106             if m:
107                 groups = m.groupdict()
108                 return ParsedUrl(
109                     groups.get("protocol"),
110                     groups.get("resource"),
111                     groups.get("pathname"),
112                     groups.get("user"),
113                     groups.get("port"),
114                     groups.get("name"),
115                     groups.get("rev"),
116                 )
117 
118         raise ValueError('Invalid git url "{}"'.format(url))
119 
120     @property
121     def url(self):  # type: () -> str
122         return "{}{}{}{}{}".format(
123             "{}://".format(self.protocol) if self.protocol else "",
124             "{}@".format(self.user) if self.user else "",
125             self.resource,
126             ":{}".format(self.port) if self.port else "",
127             "/" + self.pathname.lstrip(":/"),
128         )
129 
130     def format(self):
131         return "{}".format(self.url, "#{}".format(self.rev) if self.rev else "",)
132 
133     def __str__(self):  # type: () -> str
134         return self.format()
135 
136 
137 GitUrl = namedtuple("GitUrl", ["url", "revision"])
138 
139 
140 class GitConfig:
141     def __init__(self, requires_git_presence=False):
142         self._config = {}
143 
144         try:
145             config_list = decode(
146                 subprocess.check_output(
147                     ["git", "config", "-l"], stderr=subprocess.STDOUT
148                 )
149             )
150 
151             m = re.findall("(?ms)^([^=]+)=(.*?)$", config_list)
152             if m:
153                 for group in m:
154                     self._config[group[0]] = group[1]
155         except (subprocess.CalledProcessError, OSError):
156             if requires_git_presence:
157                 raise
158 
159     def get(self, key, default=None):
160         return self._config.get(key, default)
161 
162     def __getitem__(self, item):
163         return self._config[item]
164 
165 
166 class Git:
167     def __init__(self, work_dir=None):
168         self._config = GitConfig(requires_git_presence=True)
169         self._work_dir = work_dir
170 
171     @classmethod
172     def normalize_url(cls, url):  # type: (str) -> GitUrl
173         parsed = ParsedUrl.parse(url)
174 
175         formatted = re.sub(r"^git\+", "", url)
176         if parsed.rev:
177             formatted = re.sub(r"[#@]{}$".format(parsed.rev), "", formatted)
178 
179         altered = parsed.format() != formatted
180 
181         if altered:
182             if re.match(r"^git\+https?", url) and re.match(
183                 r"^/?:[^0-9]", parsed.pathname
184             ):
185                 normalized = re.sub(r"git\+(.*:[^:]+):(.*)", "\\1/\\2", url)
186             elif re.match(r"^git\+file", url):
187                 normalized = re.sub(r"git\+", "", url)
188             else:
189                 normalized = re.sub(r"^(?:git\+)?ssh://", "", url)
190         else:
191             normalized = parsed.format()
192 
193         return GitUrl(re.sub(r"#[^#]*$", "", normalized), parsed.rev)
194 
195     @property
196     def config(self):  # type: () -> GitConfig
197         return self._config
198 
199     def clone(self, repository, dest):  # type: (...) -> str
200         return self.run("clone", repository, str(dest))
201 
202     def checkout(self, rev, folder=None):  # type: (...) -> str
203         args = []
204         if folder is None and self._work_dir:
205             folder = self._work_dir
206 
207         if folder:
208             args += [
209                 "--git-dir",
210                 (folder / ".git").as_posix(),
211                 "--work-tree",
212                 folder.as_posix(),
213             ]
214 
215         args += ["checkout", rev]
216 
217         return self.run(*args)
218 
219     def rev_parse(self, rev, folder=None):  # type: (...) -> str
220         args = []
221         if folder is None and self._work_dir:
222             folder = self._work_dir
223 
224         if folder:
225             args += [
226                 "--git-dir",
227                 (folder / ".git").as_posix(),
228                 "--work-tree",
229                 folder.as_posix(),
230             ]
231 
232         args += ["rev-parse", rev]
233 
234         return self.run(*args)
235 
236     def get_ignored_files(self, folder=None):  # type: (...) -> list
237         args = []
238         if folder is None and self._work_dir:
239             folder = self._work_dir
240 
241         if folder:
242             args += [
243                 "--git-dir",
244                 (folder / ".git").as_posix(),
245                 "--work-tree",
246                 folder.as_posix(),
247             ]
248 
249         args += ["ls-files", "--others", "-i", "--exclude-standard"]
250         output = self.run(*args)
251 
252         return output.strip().split("\n")
253 
254     def remote_urls(self, folder=None):  # type: (...) -> dict
255         output = self.run(
256             "config", "--get-regexp", r"remote\..*\.url", folder=folder
257         ).strip()
258 
259         urls = {}
260         for url in output.splitlines():
261             name, url = url.split(" ", 1)
262             urls[name.strip()] = url.strip()
263 
264         return urls
265 
266     def remote_url(self, folder=None):  # type: (...) -> str
267         urls = self.remote_urls(folder=folder)
268 
269         return urls.get("remote.origin.url", urls[list(urls.keys())[0]])
270 
271     def run(self, *args, **kwargs):  # type: (...) -> str
272         folder = kwargs.pop("folder", None)
273         if folder:
274             args = (
275                 "--git-dir",
276                 (folder / ".git").as_posix(),
277                 "--work-tree",
278                 folder.as_posix(),
279             ) + args
280 
281         return decode(
282             subprocess.check_output(["git"] + list(args), stderr=subprocess.STDOUT)
283         ).strip()
284
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/poetry/vcs/git.py b/poetry/vcs/git.py
--- a/poetry/vcs/git.py
+++ b/poetry/vcs/git.py
@@ -229,7 +229,9 @@
                 folder.as_posix(),
             ]
 
-        args += ["rev-parse", rev]
+        # We need "^{commit}" to ensure that the commit SHA of the commit the
+        # tag points to is returned, even in the case of annotated tags.
+        args += ["rev-parse", rev + "^{commit}"]
 
         return self.run(*args)

{"golden_diff": "diff --git a/poetry/vcs/git.py b/poetry/vcs/git.py\n--- a/poetry/vcs/git.py\n+++ b/poetry/vcs/git.py\n@@ -229,7 +229,9 @@\n                 folder.as_posix(),\n             ]\n \n-        args += [\"rev-parse\", rev]\n+        # We need \"^{commit}\" to ensure that the commit SHA of the commit the\n+        # tag points to is returned, even in the case of annotated tags.\n+        args += [\"rev-parse\", rev + \"^{commit}\"]\n \n         return self.run(*args)\n", "issue": "poetry tries to reinstall from git when using annotated tags as references\n- [x] I am on the [latest](https://github.com/python-poetry/poetry/releases/latest) Poetry version.\r\n- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.\r\n- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).\r\n\r\n\r\n- **OS version and name**: Debian Buster\r\n- **Poetry version**: 1.0.2\r\n- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: https://gist.github.com/jrmlhermitte/bbfda76d59b820560bf2ff4a1e57d42d\r\n\r\n## Issue\r\nThis came from a discussion on issue #691 \r\n\r\nIf we re-run the gist included above, poetry will try re-installing the referred library, even though the git tag has not changed.\r\n\r\nThis was discovered on the issue mentioned above. Here is the reply from @bibz \r\n> Thanks for the MRE.\r\n\r\n> I can reproduce your example now.\r\n> You found a different bug than we had, due to how git handled signed annotated tags.\r\n\r\n> To summarise:\r\n\r\n>    The (annotated) tag v5.1.3 itself is a git object with the short sha 73f60e6\r\n>    The tree pointed to by the tag has the short sha 4056bbb\r\n\r\n> The lock file refers to the tag object, because it is what v5.1.3 resolves to directly.\r\n Since the checked-out tree has a different sha, poetry thinks it is not up-to-date (the references are      different) and then re-install it.\r\n\r\n> I think we need a new issue for this.\r\n\r\n\r\nWould it be possible to investigate this, and possibly change the behaviour to not re-install the package?\r\n\r\nThanks for the great work on this wonderful library, keep it up!\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nimport re\nimport subprocess\n\nfrom collections import namedtuple\n\nfrom poetry.utils._compat import decode\n\n\npattern_formats = {\n    \"protocol\": r\"\\w+\",\n    \"user\": r\"[a-zA-Z0-9_.-]+\",\n    \"resource\": r\"[a-zA-Z0-9_.-]+\",\n    \"port\": r\"\\d+\",\n    \"path\": r\"[\\w\\-/\\\\]+\",\n    \"name\": r\"[\\w\\-]+\",\n    \"rev\": r\"[^@#]+\",\n}\n\nPATTERNS = [\n    re.compile(\n        r\"^(git\\+)?\"\n        r\"(?P<protocol>https?|git|ssh|rsync|file)://\"\n        r\"(?:(?P<user>{user})@)?\"\n        r\"(?P<resource>{resource})?\"\n        r\"(:(?P<port>{port}))?\"\n        r\"(?P<pathname>[:/\\\\]({path}[/\\\\])?\"\n        r\"((?P<name>{name}?)(\\.git|[/\\\\])?)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            port=pattern_formats[\"port\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n    re.compile(\n        r\"(git\\+)?\"\n        r\"((?P<protocol>{protocol})://)\"\n        r\"(?:(?P<user>{user})@)?\"\n        r\"(?P<resource>{resource}:?)\"\n        r\"(:(?P<port>{port}))?\"\n        r\"(?P<pathname>({path})\"\n        r\"(?P<name>{name})(\\.git|/)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            protocol=pattern_formats[\"protocol\"],\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            port=pattern_formats[\"port\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n    re.compile(\n        r\"^(?:(?P<user>{user})@)?\"\n        r\"(?P<resource>{resource})\"\n        r\"(:(?P<port>{port}))?\"\n        r\"(?P<pathname>([:/]{path}/)\"\n        r\"(?P<name>{name})(\\.git|/)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            port=pattern_formats[\"port\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n    re.compile(\n        r\"((?P<user>{user})@)?\"\n        r\"(?P<resource>{resource})\"\n        r\"[:/]{{1,2}}\"\n        r\"(?P<pathname>({path})\"\n        r\"(?P<name>{name})(\\.git|/)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n]\n\n\nclass ParsedUrl:\n    def __init__(self, protocol, resource, pathname, user, port, name, rev):\n        self.protocol = protocol\n        self.resource = resource\n        self.pathname = pathname\n        self.user = user\n        self.port = port\n        self.name = name\n        self.rev = rev\n\n    @classmethod\n    def parse(cls, url):  # type: () -> ParsedUrl\n        for pattern in PATTERNS:\n            m = pattern.match(url)\n            if m:\n                groups = m.groupdict()\n                return ParsedUrl(\n                    groups.get(\"protocol\"),\n                    groups.get(\"resource\"),\n                    groups.get(\"pathname\"),\n                    groups.get(\"user\"),\n                    groups.get(\"port\"),\n                    groups.get(\"name\"),\n                    groups.get(\"rev\"),\n                )\n\n        raise ValueError('Invalid git url \"{}\"'.format(url))\n\n    @property\n    def url(self):  # type: () -> str\n        return \"{}{}{}{}{}\".format(\n            \"{}://\".format(self.protocol) if self.protocol else \"\",\n            \"{}@\".format(self.user) if self.user else \"\",\n            self.resource,\n            \":{}\".format(self.port) if self.port else \"\",\n            \"/\" + self.pathname.lstrip(\":/\"),\n        )\n\n    def format(self):\n        return \"{}\".format(self.url, \"#{}\".format(self.rev) if self.rev else \"\",)\n\n    def __str__(self):  # type: () -> str\n        return self.format()\n\n\nGitUrl = namedtuple(\"GitUrl\", [\"url\", \"revision\"])\n\n\nclass GitConfig:\n    def __init__(self, requires_git_presence=False):\n        self._config = {}\n\n        try:\n            config_list = decode(\n                subprocess.check_output(\n                    [\"git\", \"config\", \"-l\"], stderr=subprocess.STDOUT\n                )\n            )\n\n            m = re.findall(\"(?ms)^([^=]+)=(.*?)$\", config_list)\n            if m:\n                for group in m:\n                    self._config[group[0]] = group[1]\n        except (subprocess.CalledProcessError, OSError):\n            if requires_git_presence:\n                raise\n\n    def get(self, key, default=None):\n        return self._config.get(key, default)\n\n    def __getitem__(self, item):\n        return self._config[item]\n\n\nclass Git:\n    def __init__(self, work_dir=None):\n        self._config = GitConfig(requires_git_presence=True)\n        self._work_dir = work_dir\n\n    @classmethod\n    def normalize_url(cls, url):  # type: (str) -> GitUrl\n        parsed = ParsedUrl.parse(url)\n\n        formatted = re.sub(r\"^git\\+\", \"\", url)\n        if parsed.rev:\n            formatted = re.sub(r\"[#@]{}$\".format(parsed.rev), \"\", formatted)\n\n        altered = parsed.format() != formatted\n\n        if altered:\n            if re.match(r\"^git\\+https?\", url) and re.match(\n                r\"^/?:[^0-9]\", parsed.pathname\n            ):\n                normalized = re.sub(r\"git\\+(.*:[^:]+):(.*)\", \"\\\\1/\\\\2\", url)\n            elif re.match(r\"^git\\+file\", url):\n                normalized = re.sub(r\"git\\+\", \"\", url)\n            else:\n                normalized = re.sub(r\"^(?:git\\+)?ssh://\", \"\", url)\n        else:\n            normalized = parsed.format()\n\n        return GitUrl(re.sub(r\"#[^#]*$\", \"\", normalized), parsed.rev)\n\n    @property\n    def config(self):  # type: () -> GitConfig\n        return self._config\n\n    def clone(self, repository, dest):  # type: (...) -> str\n        return self.run(\"clone\", repository, str(dest))\n\n    def checkout(self, rev, folder=None):  # type: (...) -> str\n        args = []\n        if folder is None and self._work_dir:\n            folder = self._work_dir\n\n        if folder:\n            args += [\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ]\n\n        args += [\"checkout\", rev]\n\n        return self.run(*args)\n\n    def rev_parse(self, rev, folder=None):  # type: (...) -> str\n        args = []\n        if folder is None and self._work_dir:\n            folder = self._work_dir\n\n        if folder:\n            args += [\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ]\n\n        args += [\"rev-parse\", rev]\n\n        return self.run(*args)\n\n    def get_ignored_files(self, folder=None):  # type: (...) -> list\n        args = []\n        if folder is None and self._work_dir:\n            folder = self._work_dir\n\n        if folder:\n            args += [\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ]\n\n        args += [\"ls-files\", \"--others\", \"-i\", \"--exclude-standard\"]\n        output = self.run(*args)\n\n        return output.strip().split(\"\\n\")\n\n    def remote_urls(self, folder=None):  # type: (...) -> dict\n        output = self.run(\n            \"config\", \"--get-regexp\", r\"remote\\..*\\.url\", folder=folder\n        ).strip()\n\n        urls = {}\n        for url in output.splitlines():\n            name, url = url.split(\" \", 1)\n            urls[name.strip()] = url.strip()\n\n        return urls\n\n    def remote_url(self, folder=None):  # type: (...) -> str\n        urls = self.remote_urls(folder=folder)\n\n        return urls.get(\"remote.origin.url\", urls[list(urls.keys())[0]])\n\n    def run(self, *args, **kwargs):  # type: (...) -> str\n        folder = kwargs.pop(\"folder\", None)\n        if folder:\n            args = (\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ) + args\n\n        return decode(\n            subprocess.check_output([\"git\"] + list(args), stderr=subprocess.STDOUT)\n        ).strip()\n", "path": "poetry/vcs/git.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nimport re\nimport subprocess\n\nfrom collections import namedtuple\n\nfrom poetry.utils._compat import decode\n\n\npattern_formats = {\n    \"protocol\": r\"\\w+\",\n    \"user\": r\"[a-zA-Z0-9_.-]+\",\n    \"resource\": r\"[a-zA-Z0-9_.-]+\",\n    \"port\": r\"\\d+\",\n    \"path\": r\"[\\w\\-/\\\\]+\",\n    \"name\": r\"[\\w\\-]+\",\n    \"rev\": r\"[^@#]+\",\n}\n\nPATTERNS = [\n    re.compile(\n        r\"^(git\\+)?\"\n        r\"(?P<protocol>https?|git|ssh|rsync|file)://\"\n        r\"(?:(?P<user>{user})@)?\"\n        r\"(?P<resource>{resource})?\"\n        r\"(:(?P<port>{port}))?\"\n        r\"(?P<pathname>[:/\\\\]({path}[/\\\\])?\"\n        r\"((?P<name>{name}?)(\\.git|[/\\\\])?)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            port=pattern_formats[\"port\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n    re.compile(\n        r\"(git\\+)?\"\n        r\"((?P<protocol>{protocol})://)\"\n        r\"(?:(?P<user>{user})@)?\"\n        r\"(?P<resource>{resource}:?)\"\n        r\"(:(?P<port>{port}))?\"\n        r\"(?P<pathname>({path})\"\n        r\"(?P<name>{name})(\\.git|/)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            protocol=pattern_formats[\"protocol\"],\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            port=pattern_formats[\"port\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n    re.compile(\n        r\"^(?:(?P<user>{user})@)?\"\n        r\"(?P<resource>{resource})\"\n        r\"(:(?P<port>{port}))?\"\n        r\"(?P<pathname>([:/]{path}/)\"\n        r\"(?P<name>{name})(\\.git|/)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            port=pattern_formats[\"port\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n    re.compile(\n        r\"((?P<user>{user})@)?\"\n        r\"(?P<resource>{resource})\"\n        r\"[:/]{{1,2}}\"\n        r\"(?P<pathname>({path})\"\n        r\"(?P<name>{name})(\\.git|/)?)\"\n        r\"([@#](?P<rev>{rev}))?\"\n        r\"$\".format(\n            user=pattern_formats[\"user\"],\n            resource=pattern_formats[\"resource\"],\n            path=pattern_formats[\"path\"],\n            name=pattern_formats[\"name\"],\n            rev=pattern_formats[\"rev\"],\n        )\n    ),\n]\n\n\nclass ParsedUrl:\n    def __init__(self, protocol, resource, pathname, user, port, name, rev):\n        self.protocol = protocol\n        self.resource = resource\n        self.pathname = pathname\n        self.user = user\n        self.port = port\n        self.name = name\n        self.rev = rev\n\n    @classmethod\n    def parse(cls, url):  # type: () -> ParsedUrl\n        for pattern in PATTERNS:\n            m = pattern.match(url)\n            if m:\n                groups = m.groupdict()\n                return ParsedUrl(\n                    groups.get(\"protocol\"),\n                    groups.get(\"resource\"),\n                    groups.get(\"pathname\"),\n                    groups.get(\"user\"),\n                    groups.get(\"port\"),\n                    groups.get(\"name\"),\n                    groups.get(\"rev\"),\n                )\n\n        raise ValueError('Invalid git url \"{}\"'.format(url))\n\n    @property\n    def url(self):  # type: () -> str\n        return \"{}{}{}{}{}\".format(\n            \"{}://\".format(self.protocol) if self.protocol else \"\",\n            \"{}@\".format(self.user) if self.user else \"\",\n            self.resource,\n            \":{}\".format(self.port) if self.port else \"\",\n            \"/\" + self.pathname.lstrip(\":/\"),\n        )\n\n    def format(self):\n        return \"{}\".format(self.url, \"#{}\".format(self.rev) if self.rev else \"\",)\n\n    def __str__(self):  # type: () -> str\n        return self.format()\n\n\nGitUrl = namedtuple(\"GitUrl\", [\"url\", \"revision\"])\n\n\nclass GitConfig:\n    def __init__(self, requires_git_presence=False):\n        self._config = {}\n\n        try:\n            config_list = decode(\n                subprocess.check_output(\n                    [\"git\", \"config\", \"-l\"], stderr=subprocess.STDOUT\n                )\n            )\n\n            m = re.findall(\"(?ms)^([^=]+)=(.*?)$\", config_list)\n            if m:\n                for group in m:\n                    self._config[group[0]] = group[1]\n        except (subprocess.CalledProcessError, OSError):\n            if requires_git_presence:\n                raise\n\n    def get(self, key, default=None):\n        return self._config.get(key, default)\n\n    def __getitem__(self, item):\n        return self._config[item]\n\n\nclass Git:\n    def __init__(self, work_dir=None):\n        self._config = GitConfig(requires_git_presence=True)\n        self._work_dir = work_dir\n\n    @classmethod\n    def normalize_url(cls, url):  # type: (str) -> GitUrl\n        parsed = ParsedUrl.parse(url)\n\n        formatted = re.sub(r\"^git\\+\", \"\", url)\n        if parsed.rev:\n            formatted = re.sub(r\"[#@]{}$\".format(parsed.rev), \"\", formatted)\n\n        altered = parsed.format() != formatted\n\n        if altered:\n            if re.match(r\"^git\\+https?\", url) and re.match(\n                r\"^/?:[^0-9]\", parsed.pathname\n            ):\n                normalized = re.sub(r\"git\\+(.*:[^:]+):(.*)\", \"\\\\1/\\\\2\", url)\n            elif re.match(r\"^git\\+file\", url):\n                normalized = re.sub(r\"git\\+\", \"\", url)\n            else:\n                normalized = re.sub(r\"^(?:git\\+)?ssh://\", \"\", url)\n        else:\n            normalized = parsed.format()\n\n        return GitUrl(re.sub(r\"#[^#]*$\", \"\", normalized), parsed.rev)\n\n    @property\n    def config(self):  # type: () -> GitConfig\n        return self._config\n\n    def clone(self, repository, dest):  # type: (...) -> str\n        return self.run(\"clone\", repository, str(dest))\n\n    def checkout(self, rev, folder=None):  # type: (...) -> str\n        args = []\n        if folder is None and self._work_dir:\n            folder = self._work_dir\n\n        if folder:\n            args += [\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ]\n\n        args += [\"checkout\", rev]\n\n        return self.run(*args)\n\n    def rev_parse(self, rev, folder=None):  # type: (...) -> str\n        args = []\n        if folder is None and self._work_dir:\n            folder = self._work_dir\n\n        if folder:\n            args += [\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ]\n\n        # We need \"^{commit}\" to ensure that the commit SHA of the commit the\n        # tag points to is returned, even in the case of annotated tags.\n        args += [\"rev-parse\", rev + \"^{commit}\"]\n\n        return self.run(*args)\n\n    def get_ignored_files(self, folder=None):  # type: (...) -> list\n        args = []\n        if folder is None and self._work_dir:\n            folder = self._work_dir\n\n        if folder:\n            args += [\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ]\n\n        args += [\"ls-files\", \"--others\", \"-i\", \"--exclude-standard\"]\n        output = self.run(*args)\n\n        return output.strip().split(\"\\n\")\n\n    def remote_urls(self, folder=None):  # type: (...) -> dict\n        output = self.run(\n            \"config\", \"--get-regexp\", r\"remote\\..*\\.url\", folder=folder\n        ).strip()\n\n        urls = {}\n        for url in output.splitlines():\n            name, url = url.split(\" \", 1)\n            urls[name.strip()] = url.strip()\n\n        return urls\n\n    def remote_url(self, folder=None):  # type: (...) -> str\n        urls = self.remote_urls(folder=folder)\n\n        return urls.get(\"remote.origin.url\", urls[list(urls.keys())[0]])\n\n    def run(self, *args, **kwargs):  # type: (...) -> str\n        folder = kwargs.pop(\"folder\", None)\n        if folder:\n            args = (\n                \"--git-dir\",\n                (folder / \".git\").as_posix(),\n                \"--work-tree\",\n                folder.as_posix(),\n            ) + args\n\n        return decode(\n            subprocess.check_output([\"git\"] + list(args), stderr=subprocess.STDOUT)\n        ).strip()\n", "path": "poetry/vcs/git.py"}]}