13 |
13 |
14 from mercurial import ( |
14 from mercurial import ( |
15 node, |
15 node, |
16 pycompat, |
16 pycompat, |
17 ) |
17 ) |
18 from mercurial.utils import ( |
18 from mercurial.utils import procutil |
19 procutil, |
|
20 ) |
|
21 |
19 |
22 NamedTemporaryFile = tempfile.NamedTemporaryFile |
20 NamedTemporaryFile = tempfile.NamedTemporaryFile |
|
21 |
23 |
22 |
24 class BundleWriteException(Exception): |
23 class BundleWriteException(Exception): |
25 pass |
24 pass |
26 |
25 |
|
26 |
27 class BundleReadException(Exception): |
27 class BundleReadException(Exception): |
28 pass |
28 pass |
|
29 |
29 |
30 |
30 class abstractbundlestore(object): |
31 class abstractbundlestore(object): |
31 """Defines the interface for bundle stores. |
32 """Defines the interface for bundle stores. |
32 |
33 |
33 A bundle store is an entity that stores raw bundle data. It is a simple |
34 A bundle store is an entity that stores raw bundle data. It is a simple |
34 key-value store. However, the keys are chosen by the store. The keys can |
35 key-value store. However, the keys are chosen by the store. The keys can |
35 be any Python object understood by the corresponding bundle index (see |
36 be any Python object understood by the corresponding bundle index (see |
36 ``abstractbundleindex`` below). |
37 ``abstractbundleindex`` below). |
37 """ |
38 """ |
|
39 |
38 __metaclass__ = abc.ABCMeta |
40 __metaclass__ = abc.ABCMeta |
39 |
41 |
40 @abc.abstractmethod |
42 @abc.abstractmethod |
41 def write(self, data): |
43 def write(self, data): |
42 """Write bundle data to the store. |
44 """Write bundle data to the store. |
54 Throws BundleReadException |
56 Throws BundleReadException |
55 The returned object should be a file object supporting read() |
57 The returned object should be a file object supporting read() |
56 and close(). |
58 and close(). |
57 """ |
59 """ |
58 |
60 |
|
61 |
59 class filebundlestore(object): |
62 class filebundlestore(object): |
60 """bundle store in filesystem |
63 """bundle store in filesystem |
61 |
64 |
62 meant for storing bundles somewhere on disk and on network filesystems |
65 meant for storing bundles somewhere on disk and on network filesystems |
63 """ |
66 """ |
|
67 |
64 def __init__(self, ui, repo): |
68 def __init__(self, ui, repo): |
65 self.ui = ui |
69 self.ui = ui |
66 self.repo = repo |
70 self.repo = repo |
67 self.storepath = ui.configpath('scratchbranch', 'storepath') |
71 self.storepath = ui.configpath('scratchbranch', 'storepath') |
68 if not self.storepath: |
72 if not self.storepath: |
69 self.storepath = self.repo.vfs.join("scratchbranches", |
73 self.storepath = self.repo.vfs.join( |
70 "filebundlestore") |
74 "scratchbranches", "filebundlestore" |
|
75 ) |
71 if not os.path.exists(self.storepath): |
76 if not os.path.exists(self.storepath): |
72 os.makedirs(self.storepath) |
77 os.makedirs(self.storepath) |
73 |
78 |
74 def _dirpath(self, hashvalue): |
79 def _dirpath(self, hashvalue): |
75 """First two bytes of the hash are the name of the upper |
80 """First two bytes of the hash are the name of the upper |
97 with open(self._filepath(key), 'rb') as f: |
102 with open(self._filepath(key), 'rb') as f: |
98 return f.read() |
103 return f.read() |
99 except IOError: |
104 except IOError: |
100 return None |
105 return None |
101 |
106 |
|
107 |
102 class externalbundlestore(abstractbundlestore): |
108 class externalbundlestore(abstractbundlestore): |
103 def __init__(self, put_binary, put_args, get_binary, get_args): |
109 def __init__(self, put_binary, put_args, get_binary, get_args): |
104 """ |
110 """ |
105 `put_binary` - path to binary file which uploads bundle to external |
111 `put_binary` - path to binary file which uploads bundle to external |
106 storage and prints key to stdout |
112 storage and prints key to stdout |
118 self.get_binary = get_binary |
124 self.get_binary = get_binary |
119 |
125 |
120 def _call_binary(self, args): |
126 def _call_binary(self, args): |
121 p = subprocess.Popen( |
127 p = subprocess.Popen( |
122 pycompat.rapply(procutil.tonativestr, args), |
128 pycompat.rapply(procutil.tonativestr, args), |
123 stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
129 stdout=subprocess.PIPE, |
124 close_fds=True) |
130 stderr=subprocess.PIPE, |
|
131 close_fds=True, |
|
132 ) |
125 stdout, stderr = p.communicate() |
133 stdout, stderr = p.communicate() |
126 returncode = p.returncode |
134 returncode = p.returncode |
127 return returncode, stdout, stderr |
135 return returncode, stdout, stderr |
128 |
136 |
129 def write(self, data): |
137 def write(self, data): |
133 # with pycompat.namedtempfile() |
141 # with pycompat.namedtempfile() |
134 with NamedTemporaryFile() as temp: |
142 with NamedTemporaryFile() as temp: |
135 temp.write(data) |
143 temp.write(data) |
136 temp.flush() |
144 temp.flush() |
137 temp.seek(0) |
145 temp.seek(0) |
138 formatted_args = [arg.format(filename=temp.name) |
146 formatted_args = [ |
139 for arg in self.put_args] |
147 arg.format(filename=temp.name) for arg in self.put_args |
|
148 ] |
140 returncode, stdout, stderr = self._call_binary( |
149 returncode, stdout, stderr = self._call_binary( |
141 [self.put_binary] + formatted_args) |
150 [self.put_binary] + formatted_args |
|
151 ) |
142 |
152 |
143 if returncode != 0: |
153 if returncode != 0: |
144 raise BundleWriteException( |
154 raise BundleWriteException( |
145 'Failed to upload to external store: %s' % stderr) |
155 'Failed to upload to external store: %s' % stderr |
|
156 ) |
146 stdout_lines = stdout.splitlines() |
157 stdout_lines = stdout.splitlines() |
147 if len(stdout_lines) == 1: |
158 if len(stdout_lines) == 1: |
148 return stdout_lines[0] |
159 return stdout_lines[0] |
149 else: |
160 else: |
150 raise BundleWriteException( |
161 raise BundleWriteException( |
151 'Bad output from %s: %s' % (self.put_binary, stdout)) |
162 'Bad output from %s: %s' % (self.put_binary, stdout) |
|
163 ) |
152 |
164 |
153 def read(self, handle): |
165 def read(self, handle): |
154 # Won't work on windows because you can't open file second time without |
166 # Won't work on windows because you can't open file second time without |
155 # closing it |
167 # closing it |
156 # TODO: rewrite without str.format() and replace NamedTemporaryFile() |
168 # TODO: rewrite without str.format() and replace NamedTemporaryFile() |
157 # with pycompat.namedtempfile() |
169 # with pycompat.namedtempfile() |
158 with NamedTemporaryFile() as temp: |
170 with NamedTemporaryFile() as temp: |
159 formatted_args = [arg.format(filename=temp.name, handle=handle) |
171 formatted_args = [ |
160 for arg in self.get_args] |
172 arg.format(filename=temp.name, handle=handle) |
|
173 for arg in self.get_args |
|
174 ] |
161 returncode, stdout, stderr = self._call_binary( |
175 returncode, stdout, stderr = self._call_binary( |
162 [self.get_binary] + formatted_args) |
176 [self.get_binary] + formatted_args |
|
177 ) |
163 |
178 |
164 if returncode != 0: |
179 if returncode != 0: |
165 raise BundleReadException( |
180 raise BundleReadException( |
166 'Failed to download from external store: %s' % stderr) |
181 'Failed to download from external store: %s' % stderr |
|
182 ) |
167 return temp.read() |
183 return temp.read() |