We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Removing credentials from a previously synced GitRepository that requires them may result in hangs
### Environment
* Nautobot version (Docker tag too if applicable): 1.5.21

### Steps to Reproduce
1. For a Git repository that requires authentication (such as a private GitHub repository), configure the `GitRepository` in Nautobot, with appropriate credentials, mark it as providing `Jobs`, and sync it successfully (which happens in the Celery worker environment).
2. Edit the `GitRepository` to remove the credentials and resync it. The resync fails as expected (again, in the Celery worker environment.)
3. Restart the Nautobot server (during startup, the `post_upgrade` signal will trigger a check and potential sync of all Git repositories in the Nautobot server environment in order to ensure that Jobs are properly discovered).

### Expected Behavior

Nautobot server to start up, perhaps with logs indicating that the repository could not be synced.

### Observed Behavior

Nautobot server startup hangs, apparently because GitPython received and didn't handle the Git `Username:` prompt and is waiting indefinitely for user input that will never come.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nautobot/utilities/git.py`
Content:
```
1 """General-purpose Git utilities."""
2 
3 from collections import namedtuple
4 import logging
5 import os
6 
7 from git import Repo
8 
9 
10 logger = logging.getLogger("nautobot.utilities.git")
11 
12 # namedtuple takes a git log diff status and its accompanying text.
13 GitDiffLog = namedtuple("GitDiffLog", ["status", "text"])
14 
15 # 'A' and 'D' status are swapped because of the way the repo.git.diff was implemented
16 # e.g. 'A' actually stands for Addition but in this case is Deletion
17 GIT_STATUS_MAP = {
18     "A": "Deletion",
19     "M": "Modification",
20     "C": "Copy",
21     "D": "Addition",
22     "R": "Renaming",
23     "T": "File Type Changed",
24     "U": "File Unmerged",
25     "X": "Unknown",
26 }
27 
28 
29 def swap_status_initials(data):
30     """Swap Git status initials with its equivalent."""
31     initial, text = data.split("\t")
32     return GitDiffLog(status=GIT_STATUS_MAP.get(initial), text=text)
33 
34 
35 def convert_git_diff_log_to_list(logs):
36     """
37     Convert Git diff log into a list splitted by \\n
38 
39     Example:
40         >>> git_log = "M\tindex.html\nR\tsample.txt"
41         >>> print(convert_git_diff_log_to_list(git_log))
42         ["Modification - index.html", "Renaming - sample.txt"]
43     """
44     logs = logs.split("\n")
45     return [swap_status_initials(line) for line in logs]
46 
47 
48 class BranchDoesNotExist(Exception):
49     pass
50 
51 
52 class GitRepo:
53     def __init__(self, path, url, clone_initially=True):
54         """
55         Ensure that we have a clone of the given remote Git repository URL at the given local directory path.
56 
57         Args:
58             path (str): path to git repo
59             url (str): git repo url
60             clone_initially (bool): True if the repo needs to be cloned
61         """
62         if os.path.isdir(path):
63             self.repo = Repo(path=path)
64         elif clone_initially:
65             self.repo = Repo.clone_from(url, to_path=path)
66         else:
67             self.repo = Repo.init(path)
68             self.repo.create_remote("origin", url=url)
69 
70         if url not in self.repo.remotes.origin.urls:
71             self.repo.remotes.origin.set_url(url)
72 
73     def fetch(self):
74         self.repo.remotes.origin.fetch()
75 
76     def checkout(self, branch, commit_hexsha=None):
77         """
78         Check out the given branch, and optionally the specified commit within that branch.
79         """
80         # Short-circuit logic - do we already have this commit checked out?
81         if commit_hexsha and commit_hexsha == self.repo.head.commit.hexsha:
82             logger.debug(f"Commit {commit_hexsha} is already checked out.")
83             return commit_hexsha
84 
85         self.fetch()
86         if commit_hexsha:
87             # Sanity check - GitPython doesn't provide a handy API for this so we just call a raw Git command:
88             # $ git branch origin/<branch> --remotes --contains <commit>
89             # prints the branch name if it DOES contain the commit, and nothing if it DOES NOT contain the commit.
90             # Since we did a `fetch` and not a `pull` above, we need to check for the commit in the remote origin
91             # branch, not the local (not-yet-updated) branch.
92             if branch not in self.repo.git.branch(f"origin/{branch}", "--remotes", "--contains", commit_hexsha):
93                 raise RuntimeError(f"Requested to check out commit `{commit_hexsha}`, but it's not in branch {branch}!")
94             logger.info(f"Checking out commit `{commit_hexsha}` on branch `{branch}`...")
95             self.repo.git.checkout(commit_hexsha)
96             return commit_hexsha
97 
98         if branch in self.repo.heads:
99             branch_head = self.repo.heads[branch]
100         else:
101             try:
102                 branch_head = self.repo.create_head(branch, self.repo.remotes.origin.refs[branch])
103                 branch_head.set_tracking_branch(self.repo.remotes.origin.refs[branch])
104             except IndexError as git_error:
105                 logger.error(
106                     "Branch %s does not exist at %s. %s", branch, list(self.repo.remotes.origin.urls)[0], git_error
107                 )
108                 raise BranchDoesNotExist(
109                     f"Please create branch '{branch}' in upstream and try again."
110                     f" If this is a new repo, please add a commit before syncing. {git_error}"
111                 )
112 
113         logger.info(f"Checking out latest commit on branch `{branch}`...")
114         branch_head.checkout()
115         # No specific commit hash was given, so make sure we get the latest from origin
116         # We would use repo.remotes.origin.pull() here, but that will fail in the case where someone has
117         # force-pushed to the upstream repo since the last time we did a pull. To be safe, we reset instead.
118         self.repo.head.reset(f"origin/{branch}", index=True, working_tree=True)
119         commit_hexsha = self.repo.head.reference.commit.hexsha
120         logger.info(f"Latest commit on branch `{branch}` is `{commit_hexsha}`")
121         return commit_hexsha
122 
123     def diff_remote(self, branch):
124         logger.debug("Fetching from remote.")
125         self.fetch()
126 
127         try:
128             self.repo.remotes.origin.refs[branch]
129         except IndexError as git_error:
130             logger.error(
131                 "Branch %s does not exist at %s. %s", branch, list(self.repo.remotes.origin.urls)[0], git_error
132             )
133             raise BranchDoesNotExist(
134                 f"Please create branch '{branch}' in upstream and try again."
135                 f" If this is a new repo, please add a commit before syncing. {git_error}"
136             )
137 
138         logger.debug("Getting diff between local branch and remote branch")
139         diff = self.repo.git.diff("--name-status", f"origin/{branch}")
140         if diff:  # if diff is not empty
141             return convert_git_diff_log_to_list(diff)
142         logger.debug("No Difference")
143         return []
144
```
--- 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/nautobot/utilities/git.py b/nautobot/utilities/git.py
--- a/nautobot/utilities/git.py
+++ b/nautobot/utilities/git.py
@@ -25,6 +25,11 @@
     "X": "Unknown",
 }
 
+# Environment variables to set on appropriate `git` CLI calls
+GIT_ENVIRONMENT = {
+    "GIT_TERMINAL_PROMPT": "0",  # never prompt for user input such as credentials - important to avoid hangs!
+}
+
 
 def swap_status_initials(data):
     """Swap Git status initials with its equivalent."""
@@ -59,10 +64,12 @@
             url (str): git repo url
             clone_initially (bool): True if the repo needs to be cloned
         """
-        if os.path.isdir(path):
+        if os.path.isdir(path) and os.path.isdir(os.path.join(path, ".git")):
             self.repo = Repo(path=path)
         elif clone_initially:
-            self.repo = Repo.clone_from(url, to_path=path)
+            # Don't log `url` as it may include authentication details.
+            logger.debug("Cloning git repository to %s...", path)
+            self.repo = Repo.clone_from(url, to_path=path, env=GIT_ENVIRONMENT)
         else:
             self.repo = Repo.init(path)
             self.repo.create_remote("origin", url=url)
@@ -71,7 +78,8 @@
             self.repo.remotes.origin.set_url(url)
 
     def fetch(self):
-        self.repo.remotes.origin.fetch()
+        with self.repo.git.custom_environment(**GIT_ENVIRONMENT):
+            self.repo.remotes.origin.fetch()
 
     def checkout(self, branch, commit_hexsha=None):
         """

{"golden_diff": "diff --git a/nautobot/utilities/git.py b/nautobot/utilities/git.py\n--- a/nautobot/utilities/git.py\n+++ b/nautobot/utilities/git.py\n@@ -25,6 +25,11 @@\n     \"X\": \"Unknown\",\n }\n \n+# Environment variables to set on appropriate `git` CLI calls\n+GIT_ENVIRONMENT = {\n+    \"GIT_TERMINAL_PROMPT\": \"0\",  # never prompt for user input such as credentials - important to avoid hangs!\n+}\n+\n \n def swap_status_initials(data):\n     \"\"\"Swap Git status initials with its equivalent.\"\"\"\n@@ -59,10 +64,12 @@\n             url (str): git repo url\n             clone_initially (bool): True if the repo needs to be cloned\n         \"\"\"\n-        if os.path.isdir(path):\n+        if os.path.isdir(path) and os.path.isdir(os.path.join(path, \".git\")):\n             self.repo = Repo(path=path)\n         elif clone_initially:\n-            self.repo = Repo.clone_from(url, to_path=path)\n+            # Don't log `url` as it may include authentication details.\n+            logger.debug(\"Cloning git repository to %s...\", path)\n+            self.repo = Repo.clone_from(url, to_path=path, env=GIT_ENVIRONMENT)\n         else:\n             self.repo = Repo.init(path)\n             self.repo.create_remote(\"origin\", url=url)\n@@ -71,7 +78,8 @@\n             self.repo.remotes.origin.set_url(url)\n \n     def fetch(self):\n-        self.repo.remotes.origin.fetch()\n+        with self.repo.git.custom_environment(**GIT_ENVIRONMENT):\n+            self.repo.remotes.origin.fetch()\n \n     def checkout(self, branch, commit_hexsha=None):\n         \"\"\"\n", "issue": "Removing credentials from a previously synced GitRepository that requires them may result in hangs\n### Environment\r\n* Nautobot version (Docker tag too if applicable): 1.5.21\r\n\r\n### Steps to Reproduce\r\n1. For a Git repository that requires authentication (such as a private GitHub repository), configure the `GitRepository` in Nautobot, with appropriate credentials, mark it as providing `Jobs`, and sync it successfully (which happens in the Celery worker environment).\r\n2. Edit the `GitRepository` to remove the credentials and resync it. The resync fails as expected (again, in the Celery worker environment.)\r\n3. Restart the Nautobot server (during startup, the `post_upgrade` signal will trigger a check and potential sync of all Git repositories in the Nautobot server environment in order to ensure that Jobs are properly discovered).\r\n\r\n### Expected Behavior\r\n\r\nNautobot server to start up, perhaps with logs indicating that the repository could not be synced.\r\n\r\n### Observed Behavior\r\n\r\nNautobot server startup hangs, apparently because GitPython received and didn't handle the Git `Username:` prompt and is waiting indefinitely for user input that will never come.\n", "before_files": [{"content": "\"\"\"General-purpose Git utilities.\"\"\"\n\nfrom collections import namedtuple\nimport logging\nimport os\n\nfrom git import Repo\n\n\nlogger = logging.getLogger(\"nautobot.utilities.git\")\n\n# namedtuple takes a git log diff status and its accompanying text.\nGitDiffLog = namedtuple(\"GitDiffLog\", [\"status\", \"text\"])\n\n# 'A' and 'D' status are swapped because of the way the repo.git.diff was implemented\n# e.g. 'A' actually stands for Addition but in this case is Deletion\nGIT_STATUS_MAP = {\n    \"A\": \"Deletion\",\n    \"M\": \"Modification\",\n    \"C\": \"Copy\",\n    \"D\": \"Addition\",\n    \"R\": \"Renaming\",\n    \"T\": \"File Type Changed\",\n    \"U\": \"File Unmerged\",\n    \"X\": \"Unknown\",\n}\n\n\ndef swap_status_initials(data):\n    \"\"\"Swap Git status initials with its equivalent.\"\"\"\n    initial, text = data.split(\"\\t\")\n    return GitDiffLog(status=GIT_STATUS_MAP.get(initial), text=text)\n\n\ndef convert_git_diff_log_to_list(logs):\n    \"\"\"\n    Convert Git diff log into a list splitted by \\\\n\n\n    Example:\n        >>> git_log = \"M\\tindex.html\\nR\\tsample.txt\"\n        >>> print(convert_git_diff_log_to_list(git_log))\n        [\"Modification - index.html\", \"Renaming - sample.txt\"]\n    \"\"\"\n    logs = logs.split(\"\\n\")\n    return [swap_status_initials(line) for line in logs]\n\n\nclass BranchDoesNotExist(Exception):\n    pass\n\n\nclass GitRepo:\n    def __init__(self, path, url, clone_initially=True):\n        \"\"\"\n        Ensure that we have a clone of the given remote Git repository URL at the given local directory path.\n\n        Args:\n            path (str): path to git repo\n            url (str): git repo url\n            clone_initially (bool): True if the repo needs to be cloned\n        \"\"\"\n        if os.path.isdir(path):\n            self.repo = Repo(path=path)\n        elif clone_initially:\n            self.repo = Repo.clone_from(url, to_path=path)\n        else:\n            self.repo = Repo.init(path)\n            self.repo.create_remote(\"origin\", url=url)\n\n        if url not in self.repo.remotes.origin.urls:\n            self.repo.remotes.origin.set_url(url)\n\n    def fetch(self):\n        self.repo.remotes.origin.fetch()\n\n    def checkout(self, branch, commit_hexsha=None):\n        \"\"\"\n        Check out the given branch, and optionally the specified commit within that branch.\n        \"\"\"\n        # Short-circuit logic - do we already have this commit checked out?\n        if commit_hexsha and commit_hexsha == self.repo.head.commit.hexsha:\n            logger.debug(f\"Commit {commit_hexsha} is already checked out.\")\n            return commit_hexsha\n\n        self.fetch()\n        if commit_hexsha:\n            # Sanity check - GitPython doesn't provide a handy API for this so we just call a raw Git command:\n            # $ git branch origin/<branch> --remotes --contains <commit>\n            # prints the branch name if it DOES contain the commit, and nothing if it DOES NOT contain the commit.\n            # Since we did a `fetch` and not a `pull` above, we need to check for the commit in the remote origin\n            # branch, not the local (not-yet-updated) branch.\n            if branch not in self.repo.git.branch(f\"origin/{branch}\", \"--remotes\", \"--contains\", commit_hexsha):\n                raise RuntimeError(f\"Requested to check out commit `{commit_hexsha}`, but it's not in branch {branch}!\")\n            logger.info(f\"Checking out commit `{commit_hexsha}` on branch `{branch}`...\")\n            self.repo.git.checkout(commit_hexsha)\n            return commit_hexsha\n\n        if branch in self.repo.heads:\n            branch_head = self.repo.heads[branch]\n        else:\n            try:\n                branch_head = self.repo.create_head(branch, self.repo.remotes.origin.refs[branch])\n                branch_head.set_tracking_branch(self.repo.remotes.origin.refs[branch])\n            except IndexError as git_error:\n                logger.error(\n                    \"Branch %s does not exist at %s. %s\", branch, list(self.repo.remotes.origin.urls)[0], git_error\n                )\n                raise BranchDoesNotExist(\n                    f\"Please create branch '{branch}' in upstream and try again.\"\n                    f\" If this is a new repo, please add a commit before syncing. {git_error}\"\n                )\n\n        logger.info(f\"Checking out latest commit on branch `{branch}`...\")\n        branch_head.checkout()\n        # No specific commit hash was given, so make sure we get the latest from origin\n        # We would use repo.remotes.origin.pull() here, but that will fail in the case where someone has\n        # force-pushed to the upstream repo since the last time we did a pull. To be safe, we reset instead.\n        self.repo.head.reset(f\"origin/{branch}\", index=True, working_tree=True)\n        commit_hexsha = self.repo.head.reference.commit.hexsha\n        logger.info(f\"Latest commit on branch `{branch}` is `{commit_hexsha}`\")\n        return commit_hexsha\n\n    def diff_remote(self, branch):\n        logger.debug(\"Fetching from remote.\")\n        self.fetch()\n\n        try:\n            self.repo.remotes.origin.refs[branch]\n        except IndexError as git_error:\n            logger.error(\n                \"Branch %s does not exist at %s. %s\", branch, list(self.repo.remotes.origin.urls)[0], git_error\n            )\n            raise BranchDoesNotExist(\n                f\"Please create branch '{branch}' in upstream and try again.\"\n                f\" If this is a new repo, please add a commit before syncing. {git_error}\"\n            )\n\n        logger.debug(\"Getting diff between local branch and remote branch\")\n        diff = self.repo.git.diff(\"--name-status\", f\"origin/{branch}\")\n        if diff:  # if diff is not empty\n            return convert_git_diff_log_to_list(diff)\n        logger.debug(\"No Difference\")\n        return []\n", "path": "nautobot/utilities/git.py"}], "after_files": [{"content": "\"\"\"General-purpose Git utilities.\"\"\"\n\nfrom collections import namedtuple\nimport logging\nimport os\n\nfrom git import Repo\n\n\nlogger = logging.getLogger(\"nautobot.utilities.git\")\n\n# namedtuple takes a git log diff status and its accompanying text.\nGitDiffLog = namedtuple(\"GitDiffLog\", [\"status\", \"text\"])\n\n# 'A' and 'D' status are swapped because of the way the repo.git.diff was implemented\n# e.g. 'A' actually stands for Addition but in this case is Deletion\nGIT_STATUS_MAP = {\n    \"A\": \"Deletion\",\n    \"M\": \"Modification\",\n    \"C\": \"Copy\",\n    \"D\": \"Addition\",\n    \"R\": \"Renaming\",\n    \"T\": \"File Type Changed\",\n    \"U\": \"File Unmerged\",\n    \"X\": \"Unknown\",\n}\n\n# Environment variables to set on appropriate `git` CLI calls\nGIT_ENVIRONMENT = {\n    \"GIT_TERMINAL_PROMPT\": \"0\",  # never prompt for user input such as credentials - important to avoid hangs!\n}\n\n\ndef swap_status_initials(data):\n    \"\"\"Swap Git status initials with its equivalent.\"\"\"\n    initial, text = data.split(\"\\t\")\n    return GitDiffLog(status=GIT_STATUS_MAP.get(initial), text=text)\n\n\ndef convert_git_diff_log_to_list(logs):\n    \"\"\"\n    Convert Git diff log into a list splitted by \\\\n\n\n    Example:\n        >>> git_log = \"M\\tindex.html\\nR\\tsample.txt\"\n        >>> print(convert_git_diff_log_to_list(git_log))\n        [\"Modification - index.html\", \"Renaming - sample.txt\"]\n    \"\"\"\n    logs = logs.split(\"\\n\")\n    return [swap_status_initials(line) for line in logs]\n\n\nclass BranchDoesNotExist(Exception):\n    pass\n\n\nclass GitRepo:\n    def __init__(self, path, url, clone_initially=True):\n        \"\"\"\n        Ensure that we have a clone of the given remote Git repository URL at the given local directory path.\n\n        Args:\n            path (str): path to git repo\n            url (str): git repo url\n            clone_initially (bool): True if the repo needs to be cloned\n        \"\"\"\n        if os.path.isdir(path) and os.path.isdir(os.path.join(path, \".git\")):\n            self.repo = Repo(path=path)\n        elif clone_initially:\n            # Don't log `url` as it may include authentication details.\n            logger.debug(\"Cloning git repository to %s...\", path)\n            self.repo = Repo.clone_from(url, to_path=path, env=GIT_ENVIRONMENT)\n        else:\n            self.repo = Repo.init(path)\n            self.repo.create_remote(\"origin\", url=url)\n\n        if url not in self.repo.remotes.origin.urls:\n            self.repo.remotes.origin.set_url(url)\n\n    def fetch(self):\n        with self.repo.git.custom_environment(**GIT_ENVIRONMENT):\n            self.repo.remotes.origin.fetch()\n\n    def checkout(self, branch, commit_hexsha=None):\n        \"\"\"\n        Check out the given branch, and optionally the specified commit within that branch.\n        \"\"\"\n        # Short-circuit logic - do we already have this commit checked out?\n        if commit_hexsha and commit_hexsha == self.repo.head.commit.hexsha:\n            logger.debug(f\"Commit {commit_hexsha} is already checked out.\")\n            return commit_hexsha\n\n        self.fetch()\n        if commit_hexsha:\n            # Sanity check - GitPython doesn't provide a handy API for this so we just call a raw Git command:\n            # $ git branch origin/<branch> --remotes --contains <commit>\n            # prints the branch name if it DOES contain the commit, and nothing if it DOES NOT contain the commit.\n            # Since we did a `fetch` and not a `pull` above, we need to check for the commit in the remote origin\n            # branch, not the local (not-yet-updated) branch.\n            if branch not in self.repo.git.branch(f\"origin/{branch}\", \"--remotes\", \"--contains\", commit_hexsha):\n                raise RuntimeError(f\"Requested to check out commit `{commit_hexsha}`, but it's not in branch {branch}!\")\n            logger.info(f\"Checking out commit `{commit_hexsha}` on branch `{branch}`...\")\n            self.repo.git.checkout(commit_hexsha)\n            return commit_hexsha\n\n        if branch in self.repo.heads:\n            branch_head = self.repo.heads[branch]\n        else:\n            try:\n                branch_head = self.repo.create_head(branch, self.repo.remotes.origin.refs[branch])\n                branch_head.set_tracking_branch(self.repo.remotes.origin.refs[branch])\n            except IndexError as git_error:\n                logger.error(\n                    \"Branch %s does not exist at %s. %s\", branch, list(self.repo.remotes.origin.urls)[0], git_error\n                )\n                raise BranchDoesNotExist(\n                    f\"Please create branch '{branch}' in upstream and try again.\"\n                    f\" If this is a new repo, please add a commit before syncing. {git_error}\"\n                )\n\n        logger.info(f\"Checking out latest commit on branch `{branch}`...\")\n        branch_head.checkout()\n        # No specific commit hash was given, so make sure we get the latest from origin\n        # We would use repo.remotes.origin.pull() here, but that will fail in the case where someone has\n        # force-pushed to the upstream repo since the last time we did a pull. To be safe, we reset instead.\n        self.repo.head.reset(f\"origin/{branch}\", index=True, working_tree=True)\n        commit_hexsha = self.repo.head.reference.commit.hexsha\n        logger.info(f\"Latest commit on branch `{branch}` is `{commit_hexsha}`\")\n        return commit_hexsha\n\n    def diff_remote(self, branch):\n        logger.debug(\"Fetching from remote.\")\n        self.fetch()\n\n        try:\n            self.repo.remotes.origin.refs[branch]\n        except IndexError as git_error:\n            logger.error(\n                \"Branch %s does not exist at %s. %s\", branch, list(self.repo.remotes.origin.urls)[0], git_error\n            )\n            raise BranchDoesNotExist(\n                f\"Please create branch '{branch}' in upstream and try again.\"\n                f\" If this is a new repo, please add a commit before syncing. {git_error}\"\n            )\n\n        logger.debug(\"Getting diff between local branch and remote branch\")\n        diff = self.repo.git.diff(\"--name-status\", f\"origin/{branch}\")\n        if diff:  # if diff is not empty\n            return convert_git_diff_log_to_list(diff)\n        logger.debug(\"No Difference\")\n        return []\n", "path": "nautobot/utilities/git.py"}]}