Merge lp:~abentley/wikkid/branch-file-store into lp:wikkid
- branch-file-store
- Merge into trunk
Proposed by
Aaron Bentley
Status: | Merged |
---|---|
Merged at revision: | 67 |
Proposed branch: | lp:~abentley/wikkid/branch-file-store |
Merge into: | lp:wikkid |
Diff against target: |
375 lines (+167/-60) 2 files modified
wikkid/filestore/bzr.py (+152/-59) wikkid/tests/test_bzr_filestore.py (+15/-1) |
To merge this branch: | bzr merge lp:~abentley/wikkid/branch-file-store |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Penhey | Approve | ||
Review via email: mp+66484@code.launchpad.net |
Commit message
Description of the change
A branch-backed file store. Certain things were refactored for reuse.
Commits are performed on the branch directly, by using a TransformPreview.
Win!
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'wikkid/filestore/bzr.py' | |||
2 | --- wikkid/filestore/bzr.py 2010-10-17 01:47:00 +0000 | |||
3 | +++ wikkid/filestore/bzr.py 2011-06-30 16:12:03 +0000 | |||
4 | @@ -11,11 +11,12 @@ | |||
5 | 11 | 11 | ||
6 | 12 | from zope.interface import implements | 12 | from zope.interface import implements |
7 | 13 | 13 | ||
9 | 14 | from bzrlib.errors import BinaryFile | 14 | from bzrlib.errors import BinaryFile, MalformedTransform |
10 | 15 | from bzrlib.merge3 import Merge3 | 15 | from bzrlib.merge3 import Merge3 |
12 | 16 | from bzrlib.osutils import split_lines | 16 | from bzrlib.osutils import splitpath, split_lines |
13 | 17 | from bzrlib.revision import NULL_REVISION | 17 | from bzrlib.revision import NULL_REVISION |
15 | 18 | from bzrlib.textfile import check_text_path | 18 | from bzrlib.textfile import check_text_lines |
16 | 19 | from bzrlib.transform import FinalPaths, TransformPreview | ||
17 | 19 | from bzrlib.urlutils import basename, dirname, joinpath | 20 | from bzrlib.urlutils import basename, dirname, joinpath |
18 | 20 | 21 | ||
19 | 21 | from wikkid.errors import FileExists, UpdateConflicts | 22 | from wikkid.errors import FileExists, UpdateConflicts |
20 | @@ -38,18 +39,61 @@ | |||
21 | 38 | return '\n' | 39 | return '\n' |
22 | 39 | 40 | ||
23 | 40 | 41 | ||
24 | 42 | def get_commit_message(commit_message): | ||
25 | 43 | if commit_message is None or commit_message.strip() == '': | ||
26 | 44 | return 'No description of change given.' | ||
27 | 45 | return commit_message | ||
28 | 46 | |||
29 | 47 | |||
30 | 48 | def normalize_content(content): | ||
31 | 49 | # Default to simple '\n' line endings. | ||
32 | 50 | content = normalize_line_endings(content) | ||
33 | 51 | # Make sure the content ends with a new-line. This makes | ||
34 | 52 | # end of file conflicts nicer. | ||
35 | 53 | if not content.endswith('\n'): | ||
36 | 54 | content += '\n' | ||
37 | 55 | return content | ||
38 | 56 | |||
39 | 57 | |||
40 | 58 | def iter_paths(path): | ||
41 | 59 | path_segments = splitpath(path) | ||
42 | 60 | while len(path_segments) > 0: | ||
43 | 61 | tail = path_segments.pop() | ||
44 | 62 | if len(path_segments) == 0: | ||
45 | 63 | yield '', tail | ||
46 | 64 | else: | ||
47 | 65 | yield joinpath(*path_segments), tail | ||
48 | 66 | |||
49 | 67 | |||
50 | 68 | def create_parents(tt, path, trans_id): | ||
51 | 69 | prev_trans_id = trans_id | ||
52 | 70 | for parent_path, tail in iter_paths(path): | ||
53 | 71 | trans_id = tt.trans_id_tree_path(parent_path) | ||
54 | 72 | tt.adjust_path(tail, trans_id, prev_trans_id) | ||
55 | 73 | if tt.tree_kind(trans_id) is not None: | ||
56 | 74 | break | ||
57 | 75 | tt.create_directory(trans_id) | ||
58 | 76 | tt.version_file(trans_id, trans_id) | ||
59 | 77 | prev_name = tail | ||
60 | 78 | prev_trans_id = trans_id | ||
61 | 79 | |||
62 | 80 | |||
63 | 41 | class FileStore(object): | 81 | class FileStore(object): |
64 | 42 | """Wraps a Bazaar branch to be a filestore.""" | 82 | """Wraps a Bazaar branch to be a filestore.""" |
65 | 43 | 83 | ||
66 | 44 | implements(IFileStore) | 84 | implements(IFileStore) |
67 | 45 | 85 | ||
70 | 46 | def __init__(self, working_tree): | 86 | def __init__(self, tree): |
71 | 47 | self.working_tree = working_tree | 87 | self.tree = tree |
72 | 88 | self.branch = tree.branch | ||
73 | 48 | self.logger = logging.getLogger('wikkid') | 89 | self.logger = logging.getLogger('wikkid') |
74 | 49 | 90 | ||
75 | 91 | def basis_tree(self): | ||
76 | 92 | return self.tree.basis_tree() | ||
77 | 93 | |||
78 | 50 | def get_file(self, path): | 94 | def get_file(self, path): |
79 | 51 | """Return an object representing the file at specified path.""" | 95 | """Return an object representing the file at specified path.""" |
81 | 52 | file_id = self.working_tree.path2id(path) | 96 | file_id = self.tree.path2id(path) |
82 | 53 | if file_id is None: | 97 | if file_id is None: |
83 | 54 | return None | 98 | return None |
84 | 55 | else: | 99 | else: |
85 | @@ -62,16 +106,15 @@ | |||
86 | 62 | This is going to be really interesting when we need to deal with | 106 | This is going to be really interesting when we need to deal with |
87 | 63 | conflicts. | 107 | conflicts. |
88 | 64 | """ | 108 | """ |
91 | 65 | if commit_message is None or commit_message.strip() == '': | 109 | commit_message = get_commit_message(commit_message) |
90 | 66 | commit_message = 'No description of change given.' | ||
92 | 67 | if parent_revision is None: | 110 | if parent_revision is None: |
93 | 68 | parent_revision = NULL_REVISION | 111 | parent_revision = NULL_REVISION |
94 | 69 | # Firstly we want to lock the tree for writing. | 112 | # Firstly we want to lock the tree for writing. |
96 | 70 | self.working_tree.lock_write() | 113 | self.tree.lock_write() |
97 | 71 | try: | 114 | try: |
98 | 72 | # Look to see if the path is there. If it is then we are doing an | 115 | # Look to see if the path is there. If it is then we are doing an |
99 | 73 | # update. If it isn't we are doing an add. | 116 | # update. If it isn't we are doing an add. |
101 | 74 | file_id = self.working_tree.path2id(path) | 117 | file_id = self.tree.path2id(path) |
102 | 75 | if file_id is None: | 118 | if file_id is None: |
103 | 76 | self._add_file(path, content, author, commit_message) | 119 | self._add_file(path, content, author, commit_message) |
104 | 77 | else: | 120 | else: |
105 | @@ -80,7 +123,7 @@ | |||
106 | 80 | file_id, path, content, author, parent_revision, | 123 | file_id, path, content, author, parent_revision, |
107 | 81 | commit_message) | 124 | commit_message) |
108 | 82 | finally: | 125 | finally: |
110 | 83 | self.working_tree.unlock() | 126 | self.tree.unlock() |
111 | 84 | 127 | ||
112 | 85 | def _ensure_directory_or_nonexistant(self, dir_path): | 128 | def _ensure_directory_or_nonexistant(self, dir_path): |
113 | 86 | """Ensure the dir_path defines a directory or doesn't exist. | 129 | """Ensure the dir_path defines a directory or doesn't exist. |
114 | @@ -106,13 +149,8 @@ | |||
115 | 106 | 149 | ||
116 | 107 | Then commit this new file with the specified commit_message. | 150 | Then commit this new file with the specified commit_message. |
117 | 108 | """ | 151 | """ |
125 | 109 | # Default to simple '\n' line endings. | 152 | content = normalize_content(content) |
126 | 110 | content = normalize_line_endings(content) | 153 | t = self.tree.bzrdir.root_transport |
120 | 111 | # Make sure the content ends with a new-line. This makes | ||
121 | 112 | # end of file conflicts nicer. | ||
122 | 113 | if not content.endswith('\n'): | ||
123 | 114 | content += '\n' | ||
124 | 115 | t = self.working_tree.bzrdir.root_transport | ||
127 | 116 | # Get a transport for the path we want. | 154 | # Get a transport for the path we want. |
128 | 117 | self._ensure_directory_or_nonexistant(dirname(path)) | 155 | self._ensure_directory_or_nonexistant(dirname(path)) |
129 | 118 | t = t.clone(dirname(path)) | 156 | t = t.clone(dirname(path)) |
130 | @@ -120,11 +158,37 @@ | |||
131 | 120 | # Put the file there. | 158 | # Put the file there. |
132 | 121 | # TODO: UTF-8 encode text files? | 159 | # TODO: UTF-8 encode text files? |
133 | 122 | t.put_bytes(basename(path), content) | 160 | t.put_bytes(basename(path), content) |
136 | 123 | self.working_tree.smart_add([t.local_abspath('.')]) | 161 | self.tree.smart_add([t.local_abspath('.')]) |
137 | 124 | self.working_tree.commit( | 162 | self.tree.commit( |
138 | 125 | message=commit_message, | 163 | message=commit_message, |
139 | 126 | authors=[author]) | 164 | authors=[author]) |
140 | 127 | 165 | ||
141 | 166 | def _get_final_text(self, content, f, parent_revision): | ||
142 | 167 | current_rev = f.last_modified_in_revision | ||
143 | 168 | wt = self.tree | ||
144 | 169 | current_lines = wt.get_file_lines(f.file_id) | ||
145 | 170 | basis = self.branch.repository.revision_tree(parent_revision) | ||
146 | 171 | basis_lines = basis.get_file_lines(f.file_id) | ||
147 | 172 | # need to break content into lines. | ||
148 | 173 | ending = get_line_ending(current_lines) | ||
149 | 174 | # If the content doesn't end with a new line, add one. | ||
150 | 175 | new_lines = split_lines(content) | ||
151 | 176 | # Look at the end of the first string. | ||
152 | 177 | new_ending = get_line_ending(new_lines) | ||
153 | 178 | if ending != new_ending: | ||
154 | 179 | # I know this is horribly inefficient, but lets get it working | ||
155 | 180 | # first. | ||
156 | 181 | content = normalize_line_endings(content, ending) | ||
157 | 182 | new_lines = split_lines(content) | ||
158 | 183 | if len(new_lines) > 0 and not new_lines[-1].endswith(ending): | ||
159 | 184 | new_lines[-1] += ending | ||
160 | 185 | merge = Merge3(basis_lines, new_lines, current_lines) | ||
161 | 186 | result = list(merge.merge_lines()) # or merge_regions or whatever | ||
162 | 187 | conflicted = ('>>>>>>>' + ending) in result | ||
163 | 188 | if conflicted: | ||
164 | 189 | raise UpdateConflicts(''.join(result), current_rev) | ||
165 | 190 | return result | ||
166 | 191 | |||
167 | 128 | def _update_file(self, file_id, path, content, author, parent_revision, | 192 | def _update_file(self, file_id, path, content, author, parent_revision, |
168 | 129 | commit_message): | 193 | commit_message): |
169 | 130 | """Update an existing file with the content. | 194 | """Update an existing file with the content. |
170 | @@ -132,36 +196,14 @@ | |||
171 | 132 | This method merges the changes in based on the parent revision. | 196 | This method merges the changes in based on the parent revision. |
172 | 133 | """ | 197 | """ |
173 | 134 | f = File(self, path, file_id) | 198 | f = File(self, path, file_id) |
176 | 135 | current_rev = f.last_modified_in_revision | 199 | wt = self.tree |
175 | 136 | wt = self.working_tree | ||
177 | 137 | wt.lock_write() | 200 | wt.lock_write() |
178 | 138 | try: | 201 | try: |
205 | 139 | current_lines = wt.get_file_lines(file_id) | 202 | result = self._get_final_text(content, f, parent_revision) |
206 | 140 | basis = wt.branch.repository.revision_tree(parent_revision) | 203 | wt.bzrdir.root_transport.put_bytes(path, ''.join(result)) |
207 | 141 | basis_lines = basis.get_file_lines(file_id) | 204 | wt.commit( |
208 | 142 | # need to break content into lines. | 205 | message=commit_message, authors=[author], |
209 | 143 | ending = get_line_ending(current_lines) | 206 | specific_files=[path]) |
184 | 144 | # If the content doesn't end with a new line, add one. | ||
185 | 145 | new_lines = split_lines(content) | ||
186 | 146 | # Look at the end of the first string. | ||
187 | 147 | new_ending = get_line_ending(new_lines) | ||
188 | 148 | if ending != new_ending: | ||
189 | 149 | # I know this is horribly inefficient, but lets get it working | ||
190 | 150 | # first. | ||
191 | 151 | content = normalize_line_endings(content, ending) | ||
192 | 152 | new_lines = split_lines(content) | ||
193 | 153 | if len(new_lines) > 0 and not new_lines[-1].endswith(ending): | ||
194 | 154 | new_lines[-1] += ending | ||
195 | 155 | merge = Merge3(basis_lines, new_lines, current_lines) | ||
196 | 156 | result = list(merge.merge_lines()) # or merge_regions or whatever | ||
197 | 157 | conflicted = ('>>>>>>>' + ending) in result | ||
198 | 158 | if conflicted: | ||
199 | 159 | raise UpdateConflicts(''.join(result), current_rev) | ||
200 | 160 | else: | ||
201 | 161 | wt.bzrdir.root_transport.put_bytes(path, ''.join(result)) | ||
202 | 162 | wt.commit( | ||
203 | 163 | message=commit_message, authors=[author], | ||
204 | 164 | specific_files=[path]) | ||
210 | 165 | finally: | 207 | finally: |
211 | 166 | wt.unlock() | 208 | wt.unlock() |
212 | 167 | 209 | ||
213 | @@ -177,7 +219,7 @@ | |||
214 | 177 | if directory is None or directory.file_type != FileType.DIRECTORY: | 219 | if directory is None or directory.file_type != FileType.DIRECTORY: |
215 | 178 | return None | 220 | return None |
216 | 179 | listing = [] | 221 | listing = [] |
218 | 180 | wt = self.working_tree | 222 | wt = self.tree |
219 | 181 | wt.lock_read() | 223 | wt.lock_read() |
220 | 182 | try: | 224 | try: |
221 | 183 | for fp, fc, fkind, fid, entry in wt.list_files( | 225 | for fp, fc, fkind, fid, entry in wt.list_files( |
222 | @@ -204,9 +246,9 @@ | |||
223 | 204 | BaseFile.__init__(self, path, file_id) | 246 | BaseFile.__init__(self, path, file_id) |
224 | 205 | self.filestore = filestore | 247 | self.filestore = filestore |
225 | 206 | # This isn't entirely necessary. | 248 | # This isn't entirely necessary. |
227 | 207 | self.working_tree = self.filestore.working_tree | 249 | self.tree = self.filestore.tree |
228 | 208 | self.file_type = self._get_filetype() | 250 | self.file_type = self._get_filetype() |
230 | 209 | bt = self.working_tree.basis_tree() | 251 | bt = self.filestore.basis_tree() |
231 | 210 | bt.lock_read() | 252 | bt.lock_read() |
232 | 211 | try: | 253 | try: |
233 | 212 | inv_file = bt.inventory[self.file_id] | 254 | inv_file = bt.inventory[self.file_id] |
234 | @@ -216,7 +258,7 @@ | |||
235 | 216 | 258 | ||
236 | 217 | def _get_filetype(self): | 259 | def _get_filetype(self): |
237 | 218 | """Work out the filetype based on the mimetype if possible.""" | 260 | """Work out the filetype based on the mimetype if possible.""" |
239 | 219 | is_directory = ('directory' == self.working_tree.kind(self.file_id)) | 261 | is_directory = ('directory' == self.tree.kind(self.file_id)) |
240 | 220 | if is_directory: | 262 | if is_directory: |
241 | 221 | return FileType.DIRECTORY | 263 | return FileType.DIRECTORY |
242 | 222 | else: | 264 | else: |
243 | @@ -232,27 +274,27 @@ | |||
244 | 232 | def get_content(self): | 274 | def get_content(self): |
245 | 233 | if self.file_id is None: | 275 | if self.file_id is None: |
246 | 234 | return None | 276 | return None |
248 | 235 | self.working_tree.lock_read() | 277 | self.tree.lock_read() |
249 | 236 | try: | 278 | try: |
250 | 237 | # basis_tree is a revision tree, queries the repositry. | 279 | # basis_tree is a revision tree, queries the repositry. |
251 | 238 | # to get the stuff off the filesystem use the working tree | 280 | # to get the stuff off the filesystem use the working tree |
252 | 239 | # which needs to start with that. WorkingTree.open('.'). | 281 | # which needs to start with that. WorkingTree.open('.'). |
253 | 240 | # branch = tree.branch. | 282 | # branch = tree.branch. |
255 | 241 | return self.working_tree.get_file_text(self.file_id) | 283 | return self.tree.get_file_text(self.file_id) |
256 | 242 | finally: | 284 | finally: |
258 | 243 | self.working_tree.unlock() | 285 | self.tree.unlock() |
259 | 244 | 286 | ||
260 | 245 | @property | 287 | @property |
261 | 246 | def last_modified_by(self): | 288 | def last_modified_by(self): |
262 | 247 | """Return the first author for the revision.""" | 289 | """Return the first author for the revision.""" |
264 | 248 | repo = self.working_tree.branch.repository | 290 | repo = self.filestore.branch.repository |
265 | 249 | rev = repo.get_revision(self.last_modified_in_revision) | 291 | rev = repo.get_revision(self.last_modified_in_revision) |
266 | 250 | return rev.get_apparent_authors()[0] | 292 | return rev.get_apparent_authors()[0] |
267 | 251 | 293 | ||
268 | 252 | @property | 294 | @property |
269 | 253 | def last_modified_date(self): | 295 | def last_modified_date(self): |
270 | 254 | """Return the last modified date for the revision.""" | 296 | """Return the last modified date for the revision.""" |
272 | 255 | repo = self.working_tree.branch.repository | 297 | repo = self.filestore.branch.repository |
273 | 256 | rev = repo.get_revision(self.last_modified_in_revision) | 298 | rev = repo.get_revision(self.last_modified_in_revision) |
274 | 257 | return datetime.utcfromtimestamp(rev.timestamp) | 299 | return datetime.utcfromtimestamp(rev.timestamp) |
275 | 258 | 300 | ||
276 | @@ -260,7 +302,12 @@ | |||
277 | 260 | def _is_binary(self): | 302 | def _is_binary(self): |
278 | 261 | """True if the file is binary.""" | 303 | """True if the file is binary.""" |
279 | 262 | try: | 304 | try: |
281 | 263 | check_text_path(self.working_tree.abspath(self.path)) | 305 | self.tree.lock_read() |
282 | 306 | try: | ||
283 | 307 | lines = self.tree.get_file_lines(self.file_id) | ||
284 | 308 | check_text_lines(lines) | ||
285 | 309 | finally: | ||
286 | 310 | self.tree.unlock() | ||
287 | 264 | return False | 311 | return False |
288 | 265 | except BinaryFile: | 312 | except BinaryFile: |
289 | 266 | return True | 313 | return True |
290 | @@ -268,7 +315,53 @@ | |||
291 | 268 | @property | 315 | @property |
292 | 269 | def is_directory(self): | 316 | def is_directory(self): |
293 | 270 | """Is this file a directory?""" | 317 | """Is this file a directory?""" |
295 | 271 | return 'directory' == self.working_tree.kind(self.file_id) | 318 | return 'directory' == self.tree.kind(self.file_id) |
296 | 272 | 319 | ||
297 | 273 | def update(self, content, user): | 320 | def update(self, content, user): |
298 | 274 | raise NotImplementedError() | 321 | raise NotImplementedError() |
299 | 322 | |||
300 | 323 | |||
301 | 324 | class BranchFileStore(FileStore): | ||
302 | 325 | |||
303 | 326 | def __init__(self, branch): | ||
304 | 327 | self.branch = branch | ||
305 | 328 | self.tree = branch.basis_tree() | ||
306 | 329 | self.logger = logging.getLogger('wikkid') | ||
307 | 330 | |||
308 | 331 | def basis_tree(self): | ||
309 | 332 | return self.tree | ||
310 | 333 | |||
311 | 334 | def update_file(self, path, content, author, parent_revision, | ||
312 | 335 | commit_message=None): | ||
313 | 336 | commit_message = get_commit_message(commit_message) | ||
314 | 337 | self.branch.lock_write() | ||
315 | 338 | try: | ||
316 | 339 | file_id = self.tree.path2id(path) | ||
317 | 340 | if file_id is not None: | ||
318 | 341 | f = File(self, path, file_id) | ||
319 | 342 | content = self._get_final_text(content, f, parent_revision) | ||
320 | 343 | else: | ||
321 | 344 | content = normalize_content(content) | ||
322 | 345 | |||
323 | 346 | with TransformPreview(self.tree) as tt: | ||
324 | 347 | trans_id = tt.trans_id_tree_path(path) | ||
325 | 348 | if tt.tree_kind(trans_id) is not None: | ||
326 | 349 | tt.delete_contents(trans_id) | ||
327 | 350 | else: | ||
328 | 351 | tt.version_file(trans_id, trans_id) | ||
329 | 352 | create_parents(tt, path, trans_id) | ||
330 | 353 | tt.create_file(content, trans_id) | ||
331 | 354 | try: | ||
332 | 355 | tt.commit(self.branch, commit_message, authors=[author]) | ||
333 | 356 | except MalformedTransform, e: | ||
334 | 357 | for conflict in e.conflicts: | ||
335 | 358 | if conflict[0] == 'non-directory parent': | ||
336 | 359 | path = FinalPaths(tt).get_path(trans_id) | ||
337 | 360 | raise FileExists( | ||
338 | 361 | '%s exists and is not a directory' % | ||
339 | 362 | conflict[1]) | ||
340 | 363 | raise | ||
341 | 364 | |||
342 | 365 | self.tree = self.branch.basis_tree() | ||
343 | 366 | finally: | ||
344 | 367 | self.branch.unlock() | ||
345 | 275 | 368 | ||
346 | === modified file 'wikkid/tests/test_bzr_filestore.py' | |||
347 | --- wikkid/tests/test_bzr_filestore.py 2010-10-17 21:04:36 +0000 | |||
348 | +++ wikkid/tests/test_bzr_filestore.py 2011-06-30 16:12:03 +0000 | |||
349 | @@ -11,7 +11,10 @@ | |||
350 | 11 | from bzrlib.tests import TestCaseWithTransport | 11 | from bzrlib.tests import TestCaseWithTransport |
351 | 12 | 12 | ||
352 | 13 | from wikkid.errors import UpdateConflicts | 13 | from wikkid.errors import UpdateConflicts |
354 | 14 | from wikkid.filestore.bzr import FileStore | 14 | from wikkid.filestore.bzr import ( |
355 | 15 | BranchFileStore, | ||
356 | 16 | FileStore, | ||
357 | 17 | ) | ||
358 | 15 | from wikkid.tests import ProvidesMixin | 18 | from wikkid.tests import ProvidesMixin |
359 | 16 | from wikkid.tests.filestore import TestFileStore | 19 | from wikkid.tests.filestore import TestFileStore |
360 | 17 | 20 | ||
361 | @@ -134,3 +137,14 @@ | |||
362 | 134 | 'test.txt', '', 'Test Author <test@example.com>', base_rev) | 137 | 'test.txt', '', 'Test Author <test@example.com>', base_rev) |
363 | 135 | curr = filestore.get_file('test.txt') | 138 | curr = filestore.get_file('test.txt') |
364 | 136 | self.assertEqual('', curr.get_content()) | 139 | self.assertEqual('', curr.get_content()) |
365 | 140 | |||
366 | 141 | |||
367 | 142 | class TestBranchFileStore(TestBzrFileStore): | ||
368 | 143 | |||
369 | 144 | def make_filestore(self, contents=None): | ||
370 | 145 | tree = self.make_branch_and_tree('.') | ||
371 | 146 | if contents: | ||
372 | 147 | self.build_tree_contents(contents) | ||
373 | 148 | tree.smart_add(['.']) | ||
374 | 149 | tree.commit(message='Initial commit', authors=['test@example.com']) | ||
375 | 150 | return BranchFileStore(tree.branch) |
Awesome, merging.