Merge lp:~jelmer/brz-git/indexlocking into lp:brz-git

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: 1923
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz-git/indexlocking
Merge into: lp:brz-git
Diff against target: 357 lines (+73/-36)
4 files modified
dir.py (+4/-11)
tests/test_workingtree.py (+5/-3)
tree.py (+12/-5)
workingtree.py (+52/-17)
To merge this branch: bzr merge lp:~jelmer/brz-git/indexlocking
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+342524@code.launchpad.net

Commit message

Use proper index locking.

Description of the change

Use proper index locking.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Rubberstamp! Proposer approves of own proposal.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dir.py'
2--- dir.py 2018-04-02 14:50:42 +0000
3+++ dir.py 2018-04-02 21:59:37 +0000
4@@ -537,14 +537,9 @@
5 if not self._git.bare:
6 from dulwich.errors import NoIndexPresent
7 repo = self.find_repository()
8- try:
9- index = repo._git.open_index()
10- except NoIndexPresent:
11- pass
12- else:
13- from .workingtree import GitWorkingTree
14- branch = self.open_branch(ref=b'HEAD', nascent_ok=True)
15- return GitWorkingTree(self, repo, branch, index)
16+ from .workingtree import GitWorkingTree
17+ branch = self.open_branch(ref=b'HEAD', nascent_ok=True)
18+ return GitWorkingTree(self, repo, branch)
19 loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
20 raise bzr_errors.NoWorkingTree(loc)
21
22@@ -584,15 +579,13 @@
23 accelerator_tree=None, hardlink=False):
24 if self._git.bare:
25 raise bzr_errors.UnsupportedOperation(self.create_workingtree, self)
26- from dulwich.index import build_index_from_tree
27 if from_branch is None:
28 from_branch = self.open_branch(nascent_ok=True)
29 if revision_id is None:
30 revision_id = from_branch.last_revision()
31 repo = self.find_repository()
32 from .workingtree import GitWorkingTree
33- index = self._git.open_index()
34- wt = GitWorkingTree(self, repo, from_branch, index)
35+ wt = GitWorkingTree(self, repo, from_branch)
36 wt.set_last_revision(revision_id)
37 wt._build_checkout_with_index()
38 return wt
39
40=== modified file 'tests/test_workingtree.py'
41--- tests/test_workingtree.py 2018-03-26 00:30:51 +0000
42+++ tests/test_workingtree.py 2018-04-02 21:59:37 +0000
43@@ -52,6 +52,7 @@
44 self.tree.add(['conflicted'])
45 with self.tree.lock_tree_write():
46 self.tree.index['conflicted'] = self.tree.index['conflicted'][:9] + (FLAG_STAGEMASK, )
47+ self.tree._index_dirty = True
48 conflicts = self.tree.conflicts()
49 self.assertEqual(1, len(conflicts))
50
51@@ -76,9 +77,10 @@
52 tree_id = store[self.wt.branch.repository._git.head()].tree
53 except KeyError:
54 tree_id = None
55- changes, extras = changes_between_git_tree_and_working_copy(
56- store, tree_id, self.wt, want_unversioned=want_unversioned)
57- self.assertEqual(expected_changes, list(changes))
58+ with self.wt.lock_read():
59+ changes, extras = changes_between_git_tree_and_working_copy(
60+ store, tree_id, self.wt, want_unversioned=want_unversioned)
61+ self.assertEqual(expected_changes, list(changes))
62 if expected_extras is None:
63 expected_extras = set()
64 self.assertEqual(set(expected_extras), set(extras))
65
66=== modified file 'tree.py'
67--- tree.py 2018-03-28 22:45:24 +0000
68+++ tree.py 2018-04-02 21:59:37 +0000
69@@ -741,6 +741,7 @@
70 self._lock_mode = None
71 self._lock_count = 0
72 self._versioned_dirs = None
73+ self._index_dirty = False
74
75 def is_versioned(self, path):
76 with self.lock_read():
77@@ -813,6 +814,10 @@
78 raise errors.InvalidNormalization(path)
79 self._index_add_entry(path, kind)
80
81+ def _index_del_entry(self, path):
82+ del self.index[path]
83+ self._index_dirty = True
84+
85 def _index_add_entry(self, path, kind, flags=0):
86 if not isinstance(path, basestring):
87 raise TypeError(path)
88@@ -852,6 +857,7 @@
89 # TODO(jelmer): Why do we need to do this?
90 trace.mutter('ignoring path with invalid newline in it: %r', path)
91 return
92+ self._index_dirty = True
93 self.index[encoded_path] = index_entry_from_stat(
94 stat_val, blob.id, flags)
95 if self._versioned_dirs is not None:
96@@ -948,13 +954,13 @@
97 encoded_path = path.encode("utf-8")
98 count = 0
99 try:
100- del self.index[encoded_path]
101+ self._index_del_entry(encoded_path)
102 except KeyError:
103 # A directory, perhaps?
104 for p in list(self.index):
105 if p.startswith(encoded_path+b"/"):
106 count += 1
107- del self.index[p]
108+ self._index_del_entry(p)
109 else:
110 count = 1
111 self._versioned_dirs = None
112@@ -975,7 +981,7 @@
113 # TODO(jelmer): This shouldn't be called, it's inventory specific.
114 for (old_path, new_path, file_id, ie) in delta:
115 if old_path is not None and old_path.encode('utf-8') in self.index:
116- del self.index[old_path.encode('utf-8')]
117+ self._index_del_entry(old_path.encode('utf-8'))
118 self._versioned_dirs = None
119 if new_path is not None and ie.kind != 'directory':
120 self._index_add_entry(new_path, ie.kind)
121@@ -1059,15 +1065,16 @@
122 raise
123 if kind != 'directory':
124 try:
125- del self.index[from_path]
126+ self._index_del_entry(from_path)
127 except KeyError:
128 pass
129 self._index_add_entry(to_rel, kind)
130 else:
131 todo = [p for p in self.index if p.startswith(from_path+'/')]
132 for p in todo:
133+ self._index_dirty = True
134 self.index[posixpath.join(to_path, posixpath.relpath(p, from_path))] = self.index[p]
135- del self.index[p]
136+ self._index_del_entry(p)
137
138 self._versioned_dirs = None
139 self.flush()
140
141=== modified file 'workingtree.py'
142--- workingtree.py 2018-03-27 03:09:48 +0000
143+++ workingtree.py 2018-04-02 21:59:37 +0000
144@@ -29,8 +29,10 @@
145 from dulwich.ignore import (
146 IgnoreFilterManager,
147 )
148+from dulwich.file import GitFile, FileLocked
149 from dulwich.index import (
150 Index,
151+ SHA1Writer,
152 build_index_from_tree,
153 changes_from_tree,
154 cleanup_mode,
155@@ -41,6 +43,7 @@
156 blob_from_path_and_stat,
157 FLAG_STAGEMASK,
158 validate_path,
159+ write_index_dict,
160 )
161 from dulwich.object_store import (
162 tree_lookup_path,
163@@ -105,7 +108,7 @@
164 class GitWorkingTree(MutableGitIndexTree,workingtree.WorkingTree):
165 """A Git working tree."""
166
167- def __init__(self, controldir, repo, branch, index):
168+ def __init__(self, controldir, repo, branch):
169 MutableGitIndexTree.__init__(self)
170 basedir = controldir.root_transport.local_abspath('.')
171 self.basedir = osutils.realpath(basedir)
172@@ -116,7 +119,8 @@
173 self._branch = branch
174 self._transport = controldir.transport
175 self._format = GitWorkingTreeFormat()
176- self.index = index
177+ self.index = None
178+ self._index_file = None
179 self.views = self._make_views()
180 self._rules_searcher = None
181 self._detect_case_handling()
182@@ -128,6 +132,10 @@
183 def supports_rename_tracking(self):
184 return False
185
186+ def _read_index(self):
187+ self.index = Index(self.control_transport.local_abspath('index'))
188+ self._index_dirty = False
189+
190 def lock_read(self):
191 """Lock the repository for read operations.
192
193@@ -136,7 +144,7 @@
194 if not self._lock_mode:
195 self._lock_mode = 'r'
196 self._lock_count = 1
197- self.index.read()
198+ self._read_index()
199 else:
200 self._lock_count += 1
201 self.branch.lock_read()
202@@ -147,7 +155,11 @@
203 if not self._lock_mode:
204 self._lock_mode = 'w'
205 self._lock_count = 1
206- self.index.read()
207+ try:
208+ self._index_file = GitFile(self.control_transport.local_abspath('index'), 'wb')
209+ except FileLocked:
210+ raise errors.LockContention('index')
211+ self._read_index()
212 elif self._lock_mode == 'r':
213 raise errors.ReadOnlyError(self)
214 else:
215@@ -177,6 +189,13 @@
216 def get_physical_lock_status(self):
217 return False
218
219+ def break_lock(self):
220+ try:
221+ self.control_transport.delete('index.lock')
222+ except errors.NoSuchFile:
223+ pass
224+ self.branch.break_lock()
225+
226 @only_raises(errors.LockNotHeld, errors.LockBroken)
227 def unlock(self):
228 if not self._lock_count:
229@@ -186,7 +205,17 @@
230 self._lock_count -= 1
231 if self._lock_count > 0:
232 return
233+ if self._index_file is not None:
234+ if self._index_dirty:
235+ self._flush(self._index_file)
236+ self._index_file.close()
237+ else:
238+ # Somebody else already wrote the index file
239+ # by calling .flush()
240+ self._index_file.abort()
241+ self._index_file = None
242 self._lock_mode = None
243+ self.index = None
244 finally:
245 self.branch.unlock()
246
247@@ -390,7 +419,6 @@
248 if message is not None:
249 trace.note(message)
250 self._versioned_dirs = None
251- self.flush()
252
253 def smart_add(self, file_list, recurse=True, action=None, save=True):
254 if not file_list:
255@@ -480,8 +508,6 @@
256 if save:
257 self._index_add_entry(subp, kind)
258 added.append(subp)
259- if added and save:
260- self.flush()
261 return added, ignored
262
263 def has_filename(self, filename):
264@@ -525,10 +551,23 @@
265 yield p
266
267 def flush(self):
268- # TODO: Maybe this should only write on dirty ?
269 if self._lock_mode != 'w':
270 raise errors.NotWriteLocked(self)
271- self.index.write()
272+ # TODO(jelmer): This shouldn't be writing in-place, but index.lock is
273+ # already in use and GitFile doesn't allow overriding the lock file name :(
274+ f = open(self.control_transport.local_abspath('index'), 'wb')
275+ # Note that _flush will close the file
276+ self._flush(f)
277+
278+ def _flush(self, f):
279+ try:
280+ shaf = SHA1Writer(f)
281+ write_index_dict(shaf, self.index)
282+ shaf.close()
283+ except:
284+ f.abort()
285+ raise
286+ self._index_dirty = False
287
288 def has_or_had_id(self, file_id):
289 if self.has_id(file_id):
290@@ -818,11 +857,11 @@
291 with self.lock_tree_write():
292 for path in self.index:
293 self._set_conflicted(path, path in by_path)
294- self.flush()
295
296 def _set_conflicted(self, path, conflicted):
297 trace.mutter('change conflict: %r -> %r', path, conflicted)
298 value = self.index[path]
299+ self._index_dirty = True
300 if conflicted:
301 self.index[path] = (value[:9] + (value[9] | FLAG_STAGEMASK, ))
302 else:
303@@ -838,7 +877,6 @@
304 raise errors.UnsupportedOperation(self.add_conflicts, self)
305 else:
306 raise errors.UnsupportedOperation(self.add_conflicts, self)
307- self.flush()
308
309 def walkdirs(self, prefix=""):
310 """Walk the directories of this tree.
311@@ -1003,14 +1041,13 @@
312 for (old_path, new_path, file_id, ie) in changes:
313 if old_path is not None:
314 try:
315- del self.index[old_path.encode('utf-8')]
316+ self._index_del_entry(old_path.encode('utf-8'))
317 except KeyError:
318 pass
319 else:
320 self._versioned_dirs = None
321 if new_path is not None and ie.kind != 'directory':
322 self._index_add_entry(new_path, ie.kind)
323- self.flush()
324
325 def annotate_iter(self, path, file_id=None,
326 default_revision=_mod_revision.CURRENT_REVISION):
327@@ -1087,6 +1124,7 @@
328 if revision_ids is not None:
329 self.set_parent_ids(revision_ids)
330 self.index.clear()
331+ self._index_dirty = True
332 if self.branch.head is not None:
333 for entry in self.store.iter_tree_contents(self.store[self.branch.head].tree):
334 if not validate_path(entry.path):
335@@ -1105,7 +1143,6 @@
336 0, 0, len(obj.as_raw_string()), 0,
337 0, 0))
338 self.index[entry.path] = index_entry_from_stat(st, entry.sha, 0)
339- self.flush()
340
341 def pull(self, source, overwrite=False, stop_revision=None,
342 change_reporter=None, possible_transports=None, local=False,
343@@ -1161,13 +1198,11 @@
344 """See WorkingTreeFormat.initialize()."""
345 if not isinstance(a_controldir, LocalGitDir):
346 raise errors.IncompatibleFormat(self, a_controldir)
347- index = Index(a_controldir.root_transport.local_abspath(".git/index"))
348- index.write()
349 branch = a_controldir.open_branch(nascent_ok=True)
350 if revision_id is not None:
351 branch.set_last_revision(revision_id)
352 wt = GitWorkingTree(
353- a_controldir, a_controldir.open_repository(), branch, index)
354+ a_controldir, a_controldir.open_repository(), branch)
355 for hook in MutableTree.hooks['post_build_tree']:
356 hook(wt)
357 return wt

Subscribers

People subscribed via source and target branches

to all changes: