##// END OF EJS Templates
infinitepush: fix `{get,put}_args` formatting on Python 3...
Connor Sheehan -
r45753:e285655c stable
parent child Browse files
Show More
@@ -1,183 +1,198 b''
1 1 # This software may be used and distributed according to the terms of the
2 2 # GNU General Public License version 2 or any later version.
3 3
4 4 # based on bundleheads extension by Gregory Szorc <gps@mozilla.com>
5 5
6 6 from __future__ import absolute_import
7 7
8 8 import abc
9 9 import os
10 10 import subprocess
11 11 import tempfile
12 12
13 13 from mercurial.pycompat import open
14 14 from mercurial import (
15 15 node,
16 16 pycompat,
17 17 )
18 18 from mercurial.utils import (
19 19 hashutil,
20 20 procutil,
21 21 )
22 22
23 23
24 24 class BundleWriteException(Exception):
25 25 pass
26 26
27 27
28 28 class BundleReadException(Exception):
29 29 pass
30 30
31 31
32 32 class abstractbundlestore(object): # pytype: disable=ignored-metaclass
33 33 """Defines the interface for bundle stores.
34 34
35 35 A bundle store is an entity that stores raw bundle data. It is a simple
36 36 key-value store. However, the keys are chosen by the store. The keys can
37 37 be any Python object understood by the corresponding bundle index (see
38 38 ``abstractbundleindex`` below).
39 39 """
40 40
41 41 __metaclass__ = abc.ABCMeta
42 42
43 43 @abc.abstractmethod
44 44 def write(self, data):
45 45 """Write bundle data to the store.
46 46
47 47 This function receives the raw data to be written as a str.
48 48 Throws BundleWriteException
49 49 The key of the written data MUST be returned.
50 50 """
51 51
52 52 @abc.abstractmethod
53 53 def read(self, key):
54 54 """Obtain bundle data for a key.
55 55
56 56 Returns None if the bundle isn't known.
57 57 Throws BundleReadException
58 58 The returned object should be a file object supporting read()
59 59 and close().
60 60 """
61 61
62 62
63 63 class filebundlestore(object):
64 64 """bundle store in filesystem
65 65
66 66 meant for storing bundles somewhere on disk and on network filesystems
67 67 """
68 68
69 69 def __init__(self, ui, repo):
70 70 self.ui = ui
71 71 self.repo = repo
72 72 self.storepath = ui.configpath(b'scratchbranch', b'storepath')
73 73 if not self.storepath:
74 74 self.storepath = self.repo.vfs.join(
75 75 b"scratchbranches", b"filebundlestore"
76 76 )
77 77 if not os.path.exists(self.storepath):
78 78 os.makedirs(self.storepath)
79 79
80 80 def _dirpath(self, hashvalue):
81 81 """First two bytes of the hash are the name of the upper
82 82 level directory, next two bytes are the name of the
83 83 next level directory"""
84 84 return os.path.join(self.storepath, hashvalue[0:2], hashvalue[2:4])
85 85
86 86 def _filepath(self, filename):
87 87 return os.path.join(self._dirpath(filename), filename)
88 88
89 89 def write(self, data):
90 90 filename = node.hex(hashutil.sha1(data).digest())
91 91 dirpath = self._dirpath(filename)
92 92
93 93 if not os.path.exists(dirpath):
94 94 os.makedirs(dirpath)
95 95
96 96 with open(self._filepath(filename), b'wb') as f:
97 97 f.write(data)
98 98
99 99 return filename
100 100
101 101 def read(self, key):
102 102 try:
103 103 with open(self._filepath(key), b'rb') as f:
104 104 return f.read()
105 105 except IOError:
106 106 return None
107 107
108 108
109 def format_placeholders_args(args, filename=None, handle=None):
110 """Formats `args` with Infinitepush replacements.
111
112 Hack to get `str.format()`-ed strings working in a BC way with
113 bytes.
114 """
115 formatted_args = []
116 for arg in args:
117 if filename and arg == b'{filename}':
118 formatted_args.append(filename)
119 elif handle and arg == b'{handle}':
120 formatted_args.append(handle)
121 else:
122 formatted_args.append(arg)
123 return formatted_args
124
125
109 126 class externalbundlestore(abstractbundlestore):
110 127 def __init__(self, put_binary, put_args, get_binary, get_args):
111 128 """
112 129 `put_binary` - path to binary file which uploads bundle to external
113 130 storage and prints key to stdout
114 131 `put_args` - format string with additional args to `put_binary`
115 132 {filename} replacement field can be used.
116 133 `get_binary` - path to binary file which accepts filename and key
117 134 (in that order), downloads bundle from store and saves it to file
118 135 `get_args` - format string with additional args to `get_binary`.
119 136 {filename} and {handle} replacement field can be used.
120 137 """
121 138
122 139 self.put_args = put_args
123 140 self.get_args = get_args
124 141 self.put_binary = put_binary
125 142 self.get_binary = get_binary
126 143
127 144 def _call_binary(self, args):
128 145 p = subprocess.Popen(
129 146 pycompat.rapply(procutil.tonativestr, args),
130 147 stdout=subprocess.PIPE,
131 148 stderr=subprocess.PIPE,
132 149 close_fds=True,
133 150 )
134 151 stdout, stderr = p.communicate()
135 152 returncode = p.returncode
136 153 return returncode, stdout, stderr
137 154
138 155 def write(self, data):
139 156 # Won't work on windows because you can't open file second time without
140 157 # closing it
141 158 # TODO: rewrite without str.format() and replace NamedTemporaryFile()
142 159 # with pycompat.namedtempfile()
143 160 with pycompat.namedtempfile() as temp:
144 161 temp.write(data)
145 162 temp.flush()
146 163 temp.seek(0)
147 formatted_args = [
148 arg.format(filename=temp.name) for arg in self.put_args
149 ]
164 formatted_args = format_placeholders_args(
165 self.put_args, filename=temp.name
166 )
150 167 returncode, stdout, stderr = self._call_binary(
151 168 [self.put_binary] + formatted_args
152 169 )
153 170
154 171 if returncode != 0:
155 172 raise BundleWriteException(
156 173 b'Failed to upload to external store: %s' % stderr
157 174 )
158 175 stdout_lines = stdout.splitlines()
159 176 if len(stdout_lines) == 1:
160 177 return stdout_lines[0]
161 178 else:
162 179 raise BundleWriteException(
163 180 b'Bad output from %s: %s' % (self.put_binary, stdout)
164 181 )
165 182
166 183 def read(self, handle):
167 184 # Won't work on windows because you can't open file second time without
168 185 # closing it
169 # TODO: rewrite without str.format()
170 186 with pycompat.namedtempfile() as temp:
171 formatted_args = [
172 arg.format(filename=temp.name, handle=handle)
173 for arg in self.get_args
174 ]
187 formatted_args = format_placeholders_args(
188 self.get_args, filename=temp.name, handle=handle
189 )
175 190 returncode, stdout, stderr = self._call_binary(
176 191 [self.get_binary] + formatted_args
177 192 )
178 193
179 194 if returncode != 0:
180 195 raise BundleReadException(
181 196 b'Failed to download from external store: %s' % stderr
182 197 )
183 198 return temp.read()
General Comments 0
You need to be logged in to leave comments. Login now