nbgitpuller 1.2.2__tar.gz → 1.3.0b1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/CHANGELOG.md +37 -0
- {nbgitpuller-1.2.2/nbgitpuller.egg-info → nbgitpuller-1.3.0b1}/PKG-INFO +3 -2
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/__init__.py +2 -2
- nbgitpuller-1.3.0b1/nbgitpuller/errors.py +117 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/handlers.py +29 -15
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/pull.py +40 -10
- nbgitpuller-1.3.0b1/nbgitpuller/static/dist/bundle.js +3 -0
- nbgitpuller-1.3.0b1/nbgitpuller/static/dist/bundle.js.LICENSE.txt +1 -0
- nbgitpuller-1.3.0b1/nbgitpuller/static/dist/bundle.js.map +1 -0
- nbgitpuller-1.3.0b1/nbgitpuller/static/js/giterror.js +61 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/static/js/gitsync.js +5 -1
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/static/js/gitsyncview.js +25 -1
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/static/js/index.js +16 -5
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/templates/status.html +28 -3
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/version.py +1 -1
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1/nbgitpuller.egg-info}/PKG-INFO +3 -2
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller.egg-info/SOURCES.txt +3 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/package.json +1 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/pyproject.toml +1 -1
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/setup.py +1 -1
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/tests/test_api.py +15 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/tests/test_gitpuller.py +69 -4
- nbgitpuller-1.2.2/nbgitpuller/static/dist/bundle.js +0 -2
- nbgitpuller-1.2.2/nbgitpuller/static/dist/bundle.js.map +0 -1
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/LICENSE +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/MANIFEST.in +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/README.md +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/RELEASE.md +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/_compat.py +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/etc/jupyter_notebook_config.d/nbgitpuller.json +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/etc/jupyter_server_config.d/nbgitpuller.json +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller/templates/page.html +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller.egg-info/dependency_links.txt +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller.egg-info/entry_points.txt +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller.egg-info/not-zip-safe +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller.egg-info/requires.txt +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/nbgitpuller.egg-info/top_level.txt +0 -0
- {nbgitpuller-1.2.2 → nbgitpuller-1.3.0b1}/setup.cfg +0 -0
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
## 1.3
|
|
2
|
+
|
|
3
|
+
### 1.3.0b1 (beta release) - 2026-02-04
|
|
4
|
+
|
|
5
|
+
([full changelog](https://github.com/jupyterhub/nbgitpuller/compare/1.2.2...7af8b80))
|
|
6
|
+
|
|
7
|
+
#### New features added
|
|
8
|
+
|
|
9
|
+
- Add `copy error to clipboard` button [#382](https://github.com/jupyterhub/nbgitpuller/pull/382) ([@jnywong](https://github.com/jnywong), [@balajialg](https://github.com/balajialg), [@yuvipanda](https://github.com/yuvipanda))
|
|
10
|
+
- Add link to go back to root when an error is thrown [#380](https://github.com/jupyterhub/nbgitpuller/pull/380) ([@cmarmo](https://github.com/cmarmo), [@jnywong](https://github.com/jnywong), [@nthiery](https://github.com/nthiery), [@yuvipanda](https://github.com/yuvipanda))
|
|
11
|
+
|
|
12
|
+
#### Documentation improvements
|
|
13
|
+
|
|
14
|
+
- Add targetPath to link generator. [#368](https://github.com/jupyterhub/nbgitpuller/pull/368) ([@ryanlovett](https://github.com/ryanlovett), [@consideRatio](https://github.com/consideRatio))
|
|
15
|
+
- docs: Add warning about private tokens [#387](https://github.com/jupyterhub/nbgitpuller/pull/387) ([@jnywong](https://github.com/jnywong), [@ryanlovett](https://github.com/ryanlovett), [@yuvipanda](https://github.com/yuvipanda))
|
|
16
|
+
- Update reference to link.rst [#363](https://github.com/jupyterhub/nbgitpuller/pull/363) ([@jnywong](https://github.com/jnywong), [@GeorgianaElena](https://github.com/GeorgianaElena))
|
|
17
|
+
|
|
18
|
+
#### Continuous integration improvements
|
|
19
|
+
|
|
20
|
+
- Bump actions/checkout from 5 to 6 [#381](https://github.com/jupyterhub/nbgitpuller/pull/381) ([@consideRatio](https://github.com/consideRatio))
|
|
21
|
+
- Bump actions/setup-node from 5 to 6 [#377](https://github.com/jupyterhub/nbgitpuller/pull/377) ([@manics](https://github.com/manics))
|
|
22
|
+
- Bump actions/setup-python from 5 to 6 [#376](https://github.com/jupyterhub/nbgitpuller/pull/376) ([@consideRatio](https://github.com/consideRatio))
|
|
23
|
+
- Bump actions/checkout from 4 to 5 [#375](https://github.com/jupyterhub/nbgitpuller/pull/375) ([@consideRatio](https://github.com/consideRatio))
|
|
24
|
+
- Bump actions/setup-node from 4 to 5 [#374](https://github.com/jupyterhub/nbgitpuller/pull/374) ([@consideRatio](https://github.com/consideRatio))
|
|
25
|
+
- Support building on mybinder.org to ease testing [#258](https://github.com/jupyterhub/nbgitpuller/pull/258) ([@manics](https://github.com/manics), [@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
|
26
|
+
- [pre-commit.ci] pre-commit autoupdate [#371](https://github.com/jupyterhub/nbgitpuller/pull/371) ([@consideRatio](https://github.com/consideRatio))
|
|
27
|
+
- [pre-commit.ci] pre-commit autoupdate [#366](https://github.com/jupyterhub/nbgitpuller/pull/366) ([@yuvipanda](https://github.com/yuvipanda))
|
|
28
|
+
|
|
29
|
+
#### Contributors to this release
|
|
30
|
+
|
|
31
|
+
The following people contributed discussions, new ideas, code and documentation contributions, and review.
|
|
32
|
+
See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports).
|
|
33
|
+
|
|
34
|
+
([GitHub contributors page for this release](https://github.com/jupyterhub/nbgitpuller/graphs/contributors?from=2025-01-27&to=2026-02-03&type=c))
|
|
35
|
+
|
|
36
|
+
@balajialg ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Abalajialg+updated%3A2025-01-27..2026-02-03&type=Issues)) | @cmarmo ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Acmarmo+updated%3A2025-01-27..2026-02-03&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3AconsideRatio+updated%3A2025-01-27..2026-02-03&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3AGeorgianaElena+updated%3A2025-01-27..2026-02-03&type=Issues)) | @jacobtomlinson ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Ajacobtomlinson+updated%3A2025-01-27..2026-02-03&type=Issues)) | @jnywong ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Ajnywong+updated%3A2025-01-27..2026-02-03&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Amanics+updated%3A2025-01-27..2026-02-03&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Aminrk+updated%3A2025-01-27..2026-02-03&type=Issues)) | @nthiery ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Anthiery+updated%3A2025-01-27..2026-02-03&type=Issues)) | @ryanlovett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Aryanlovett+updated%3A2025-01-27..2026-02-03&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fnbgitpuller+involves%3Ayuvipanda+updated%3A2025-01-27..2026-02-03&type=Issues))
|
|
37
|
+
|
|
1
38
|
## 1.2
|
|
2
39
|
|
|
3
40
|
### 1.2.2 - 2025-01-27
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: nbgitpuller
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0b1
|
|
4
4
|
Summary: Jupyter Extension to do one-way synchronization of git repositories
|
|
5
5
|
Home-page: https://github.com/jupyterhub/nbgitpuller
|
|
6
6
|
Author: Peter Veerman, YuviPanda
|
|
@@ -25,6 +25,7 @@ Dynamic: description
|
|
|
25
25
|
Dynamic: description-content-type
|
|
26
26
|
Dynamic: home-page
|
|
27
27
|
Dynamic: license
|
|
28
|
+
Dynamic: license-file
|
|
28
29
|
Dynamic: platform
|
|
29
30
|
Dynamic: requires-dist
|
|
30
31
|
Dynamic: summary
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from .version import __version__ # noqa
|
|
2
|
-
from .pull import GitPuller # noqa
|
|
1
|
+
from nbgitpuller.version import __version__ # noqa
|
|
2
|
+
from nbgitpuller.pull import GitPuller # noqa
|
|
3
3
|
from jupyter_server.utils import url_path_join
|
|
4
4
|
from tornado.web import StaticFileHandler
|
|
5
5
|
import os
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GitPullerError(Exception):
|
|
6
|
+
"""
|
|
7
|
+
Structured error class used to branch frontend UI content based on its attributes.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
code (str): Stable internal error code identifying the failure
|
|
11
|
+
type (e.g. ``"merge"``, ``"clone"``). This value is used by the
|
|
12
|
+
frontend to branch context specific UI behaviour.
|
|
13
|
+
message (str): External error message. This value is
|
|
14
|
+
safe to display in the frontend.
|
|
15
|
+
traceback (Optional[str]): Formatted traceback information captured from
|
|
16
|
+
the original exception for logging.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, code: str = "unknown", message: str = "Unexpected error occurred", traceback_message=None):
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
self.code = code
|
|
21
|
+
self.message = message
|
|
22
|
+
self.traceback = traceback_message
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
return self.message
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def to_dict(self):
|
|
30
|
+
return {
|
|
31
|
+
"code": self.code,
|
|
32
|
+
"message": self.message,
|
|
33
|
+
"traceback": self.traceback
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def format_traceback(exc):
|
|
38
|
+
tb = getattr(exc, "__traceback__", None)
|
|
39
|
+
traceback_message = ''.join(traceback.format_exception(type(exc), exc, tb)) if tb else None
|
|
40
|
+
return traceback_message
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_exception(cls, exc: Exception) -> "GitPullerError":
|
|
45
|
+
"""
|
|
46
|
+
Convert exceptions into a structured GitPullerError class.
|
|
47
|
+
"""
|
|
48
|
+
if isinstance(exc, GitPullerError):
|
|
49
|
+
return exc
|
|
50
|
+
traceback_message = cls.format_traceback(exc)
|
|
51
|
+
if isinstance(exc, subprocess.CalledProcessError):
|
|
52
|
+
# Categorise errors based on specific git commands
|
|
53
|
+
if exc.cmd[1] == "clone":
|
|
54
|
+
return CloneError(traceback_message)
|
|
55
|
+
elif len(exc.cmd) >5 and exc.cmd[5] == "merge":
|
|
56
|
+
return MergeError(traceback_message)
|
|
57
|
+
elif exc.cmd[1] == "ls-remote":
|
|
58
|
+
return RemoteError(traceback_message)
|
|
59
|
+
return cls(code="unknown", message=str(exc), traceback_message=traceback_message)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CloneError(GitPullerError):
|
|
64
|
+
code="clone"
|
|
65
|
+
message="Clone error detected"
|
|
66
|
+
|
|
67
|
+
def __init__(self, traceback_message=None):
|
|
68
|
+
super().__init__(
|
|
69
|
+
code = self.code,
|
|
70
|
+
message= self.message,
|
|
71
|
+
traceback_message=traceback_message
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class MergeError(GitPullerError):
|
|
76
|
+
code="merge"
|
|
77
|
+
message="Merge error detected"
|
|
78
|
+
|
|
79
|
+
def __init__(self, traceback_message=None):
|
|
80
|
+
super().__init__(
|
|
81
|
+
code = self.code,
|
|
82
|
+
message= self.message,
|
|
83
|
+
traceback_message=traceback_message
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
class BranchExistError(GitPullerError):
|
|
87
|
+
code="branch_exist"
|
|
88
|
+
message="Branch does not exist"
|
|
89
|
+
|
|
90
|
+
def __init__(self, traceback_message=None):
|
|
91
|
+
super().__init__(
|
|
92
|
+
code = self.code,
|
|
93
|
+
message= self.message,
|
|
94
|
+
traceback_message=traceback_message
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
class BranchResolveError(GitPullerError):
|
|
98
|
+
code="branch_resolve"
|
|
99
|
+
message="Branch name unresolved"
|
|
100
|
+
|
|
101
|
+
def __init__(self, traceback_message=None):
|
|
102
|
+
super().__init__(
|
|
103
|
+
code = self.code,
|
|
104
|
+
message= self.message,
|
|
105
|
+
traceback_message=traceback_message
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
class RemoteError(GitPullerError):
|
|
109
|
+
code="ls_remote"
|
|
110
|
+
message="Remote content unavailable"
|
|
111
|
+
|
|
112
|
+
def __init__(self, traceback_message=None):
|
|
113
|
+
super().__init__(
|
|
114
|
+
code = self.code,
|
|
115
|
+
message= self.message,
|
|
116
|
+
traceback_message=traceback_message
|
|
117
|
+
)
|
|
@@ -8,9 +8,10 @@ import os
|
|
|
8
8
|
from queue import Queue, Empty
|
|
9
9
|
import jinja2
|
|
10
10
|
|
|
11
|
-
from .pull import GitPuller
|
|
12
|
-
from .
|
|
13
|
-
from .
|
|
11
|
+
from nbgitpuller.pull import GitPuller
|
|
12
|
+
from nbgitpuller.errors import GitPullerError
|
|
13
|
+
from nbgitpuller.version import __version__
|
|
14
|
+
from nbgitpuller._compat import get_base_handler
|
|
14
15
|
|
|
15
16
|
JupyterHandler = get_base_handler()
|
|
16
17
|
|
|
@@ -57,7 +58,7 @@ class SyncHandler(JupyterHandler):
|
|
|
57
58
|
except gen.TimeoutError:
|
|
58
59
|
await self.emit({
|
|
59
60
|
'phase': 'error',
|
|
60
|
-
'message': 'Another git
|
|
61
|
+
'message': 'Another git operation is currently running, try again in a few minutes'
|
|
61
62
|
})
|
|
62
63
|
return
|
|
63
64
|
|
|
@@ -65,6 +66,7 @@ class SyncHandler(JupyterHandler):
|
|
|
65
66
|
repo = self.get_argument('repo')
|
|
66
67
|
branch = self.get_argument('branch', None)
|
|
67
68
|
depth = self.get_argument('depth', None)
|
|
69
|
+
backup = self.get_argument('backup', False)
|
|
68
70
|
if depth:
|
|
69
71
|
depth = int(depth)
|
|
70
72
|
# The default working directory is the directory from which Jupyter
|
|
@@ -84,7 +86,20 @@ class SyncHandler(JupyterHandler):
|
|
|
84
86
|
self.set_header('content-type', 'text/event-stream')
|
|
85
87
|
self.set_header('cache-control', 'no-cache')
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
try:
|
|
90
|
+
gp = GitPuller(repo, repo_dir, branch=branch, depth=depth, backup=backup, parent=self.settings['nbapp'])
|
|
91
|
+
except Exception as e:
|
|
92
|
+
err = GitPullerError.from_exception(e)
|
|
93
|
+
err.traceback = GitPullerError.format_traceback(e)
|
|
94
|
+
err_out = err.to_dict()
|
|
95
|
+
await self.emit({
|
|
96
|
+
'phase': 'error',
|
|
97
|
+
'message': err_out["message"],
|
|
98
|
+
'error': err_out,
|
|
99
|
+
'output': err_out["traceback"],
|
|
100
|
+
})
|
|
101
|
+
raise err
|
|
102
|
+
|
|
88
103
|
|
|
89
104
|
q = Queue()
|
|
90
105
|
|
|
@@ -110,17 +125,15 @@ class SyncHandler(JupyterHandler):
|
|
|
110
125
|
if progress is None:
|
|
111
126
|
break
|
|
112
127
|
if isinstance(progress, Exception):
|
|
128
|
+
e = GitPullerError.from_exception(progress)
|
|
129
|
+
err = e.to_dict()
|
|
113
130
|
await self.emit({
|
|
114
131
|
'phase': 'error',
|
|
115
132
|
'message': str(progress),
|
|
116
|
-
'
|
|
117
|
-
|
|
118
|
-
for line in traceback.format_exception(
|
|
119
|
-
type(progress), progress, progress.__traceback__
|
|
120
|
-
)
|
|
121
|
-
])
|
|
133
|
+
'error': err,
|
|
134
|
+
'output': err["traceback"]
|
|
122
135
|
})
|
|
123
|
-
|
|
136
|
+
raise e
|
|
124
137
|
|
|
125
138
|
await self.emit({'output': progress, 'phase': 'syncing'})
|
|
126
139
|
|
|
@@ -159,11 +172,12 @@ class UIHandler(JupyterHandler):
|
|
|
159
172
|
# working directory) and we end up with weird failures
|
|
160
173
|
targetpath = self.get_argument('targetpath', None) or \
|
|
161
174
|
self.get_argument('targetPath', repo.rstrip('/').split('/')[-1])
|
|
175
|
+
backup = self.get_argument('backup', False)
|
|
162
176
|
|
|
163
177
|
if urlPath:
|
|
164
|
-
path = urlPath
|
|
178
|
+
path = urlPath if not backup else os.path.join(parent_reldir)
|
|
165
179
|
else:
|
|
166
|
-
path = os.path.join(parent_reldir, targetpath, subPath)
|
|
180
|
+
path = os.path.join(parent_reldir, targetpath, subPath) if not backup else os.path.join(parent_reldir)
|
|
167
181
|
if app.lower() == 'lab':
|
|
168
182
|
path = 'lab/tree/' + path
|
|
169
183
|
elif path.lower().endswith('.ipynb'):
|
|
@@ -173,7 +187,7 @@ class UIHandler(JupyterHandler):
|
|
|
173
187
|
|
|
174
188
|
self.write(
|
|
175
189
|
jinja_env.get_template('status.html').render(
|
|
176
|
-
repo=repo, branch=branch, path=path, depth=depth, targetpath=targetpath, version=__version__,
|
|
190
|
+
repo=repo, branch=branch, path=path, depth=depth, targetpath=targetpath, backup=backup, version=__version__,
|
|
177
191
|
**self.template_namespace
|
|
178
192
|
)
|
|
179
193
|
)
|
|
@@ -4,9 +4,11 @@ import logging
|
|
|
4
4
|
import time
|
|
5
5
|
import argparse
|
|
6
6
|
import datetime
|
|
7
|
+
from itertools import count
|
|
7
8
|
from traitlets import Integer, default
|
|
8
9
|
from traitlets.config import Configurable
|
|
9
10
|
from functools import partial
|
|
11
|
+
from nbgitpuller.errors import BranchExistError, BranchResolveError
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
def execute_cmd(cmd, **kwargs):
|
|
@@ -73,17 +75,22 @@ class GitPuller(Configurable):
|
|
|
73
75
|
assert git_url
|
|
74
76
|
|
|
75
77
|
self.git_url = git_url
|
|
76
|
-
self.branch_name = kwargs.pop("branch")
|
|
78
|
+
self.branch_name = kwargs.pop("branch", None)
|
|
79
|
+
self.repo_dir = repo_dir
|
|
80
|
+
backup = kwargs.pop("backup", False)
|
|
77
81
|
|
|
78
82
|
if self.branch_name is None:
|
|
79
83
|
self.branch_name = self.resolve_default_branch()
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
else:
|
|
85
|
+
self.branch_exists(self.branch_name)
|
|
86
|
+
|
|
87
|
+
if backup and os.path.exists(self.repo_dir):
|
|
88
|
+
self.backup_repo_dir()
|
|
82
89
|
|
|
83
|
-
self.repo_dir = repo_dir
|
|
84
90
|
newargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
85
91
|
super(GitPuller, self).__init__(**newargs)
|
|
86
92
|
|
|
93
|
+
|
|
87
94
|
def branch_exists(self, branch):
|
|
88
95
|
"""
|
|
89
96
|
This checks to make sure the branch we are told to access
|
|
@@ -107,7 +114,11 @@ class GitPuller(Configurable):
|
|
|
107
114
|
_, ref = line.split()
|
|
108
115
|
refs, heads, branch_name = ref.split("/", 2)
|
|
109
116
|
branches.append(branch_name)
|
|
110
|
-
|
|
117
|
+
if branch in branches:
|
|
118
|
+
return
|
|
119
|
+
else:
|
|
120
|
+
raise BranchExistError()
|
|
121
|
+
|
|
111
122
|
|
|
112
123
|
def resolve_default_branch(self):
|
|
113
124
|
"""
|
|
@@ -127,11 +138,28 @@ class GitPuller(Configurable):
|
|
|
127
138
|
_, ref, head = line.split()
|
|
128
139
|
refs, heads, branch_name = ref.split("/", 2)
|
|
129
140
|
return branch_name
|
|
130
|
-
raise
|
|
141
|
+
raise BranchResolveError()
|
|
131
142
|
except subprocess.CalledProcessError:
|
|
132
|
-
|
|
133
|
-
logging.exception(
|
|
134
|
-
raise
|
|
143
|
+
error = BranchResolveError()
|
|
144
|
+
logging.exception(error)
|
|
145
|
+
raise error
|
|
146
|
+
|
|
147
|
+
def backup_repo_dir(self):
|
|
148
|
+
"""
|
|
149
|
+
Backup the existing repo_dir if URL parameter backup=true.
|
|
150
|
+
"""
|
|
151
|
+
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
|
152
|
+
backup_dir = f"{self.repo_dir}_backup_{timestamp}"
|
|
153
|
+
if not os.path.exists(backup_dir):
|
|
154
|
+
os.rename(self.repo_dir, backup_dir)
|
|
155
|
+
else:
|
|
156
|
+
# Never clobber backup_dir if it somehow exists
|
|
157
|
+
base_dir = backup_dir
|
|
158
|
+
candidate = (f"{base_dir}.latest_{i}" for i in count())
|
|
159
|
+
while os.path.exists(backup_dir):
|
|
160
|
+
backup_dir = next(candidate)
|
|
161
|
+
os.rename(self.repo_dir, backup_dir)
|
|
162
|
+
logging.info('Backed up folder {}'.format(backup_dir))
|
|
135
163
|
|
|
136
164
|
def pull(self):
|
|
137
165
|
"""
|
|
@@ -356,12 +384,14 @@ def main():
|
|
|
356
384
|
parser.add_argument('git_url', help='Url of the repo to sync')
|
|
357
385
|
parser.add_argument('branch_name', default=None, help='Branch of repo to sync', nargs='?')
|
|
358
386
|
parser.add_argument('repo_dir', default='.', help='Path to clone repo under', nargs='?')
|
|
387
|
+
parser.add_argument('--backup', action='store_true', default=False, help='Whether to backup existing repo_dir if it exists')
|
|
359
388
|
args = parser.parse_args()
|
|
360
389
|
|
|
361
390
|
for line in GitPuller(
|
|
362
391
|
args.git_url,
|
|
363
392
|
args.repo_dir,
|
|
364
|
-
branch=args.branch_name if args.branch_name else None
|
|
393
|
+
branch=args.branch_name if args.branch_name else None,
|
|
394
|
+
backup=args.backup if args.backup else False,
|
|
365
395
|
).pull():
|
|
366
396
|
print(line)
|
|
367
397
|
|