def remove_if_equals(self, name, old_ref, committer=None, timestamp=None, timezone=None, message=None): """Remove a refname only if it currently equals old_ref. This method does not follow symbolic references. It can be used to perform an atomic compare-and-delete operation. :param name: The refname to delete. :param old_ref: The old sha the refname must refer to, or None to delete unconditionally. :param message: Optional message :return: True if the delete was successful, False otherwise. """ self._check_refname(name) filename = self.refpath(name) ensure_dir_exists(os.path.dirname(filename)) f = GitFile(filename, 'wb') try: if old_ref is not None: orig_ref = self.read_loose_ref(name) if orig_ref is None: orig_ref = self.get_packed_refs().get(name, ZERO_SHA) if orig_ref != old_ref: return False # remove the reference file itself try: os.remove(filename) except OSError as e: if e.errno != errno.ENOENT: # may only be packed raise self._remove_packed_ref(name) self._log(name, old_ref, None, committer=committer, timestamp=timestamp, timezone=timezone, message=message) finally: # never write, we just wanted the lock f.abort() # outside of the lock, clean-up any parent directory that might now # be empty. this ensures that re-creating a reference of the same # name of what was previously a directory works as expected parent = name while True: try: parent, _ = parent.rsplit(b'/', 1) except ValueError: break parent_filename = self.refpath(parent) try: os.rmdir(parent_filename) except OSError as e: # this can be caused by the parent directory being # removed by another process, being not empty, etc. # in any case, this is non fatal because we already # removed the reference, just ignore it break return True
def test_abort_close_removed(self): foo = self.path('foo') f = GitFile(foo, 'wb') f._file.close() os.remove(foo + ".lock") f.abort() self.assertTrue(f._closed)
def test_abort_close(self): foo = self.path('foo') f = GitFile(foo, 'wb') f.abort() try: f.close() except (IOError, OSError): self.fail() f = GitFile(foo, 'wb') f.close() try: f.abort() except (IOError, OSError): self.fail()
def test_abort(self): foo = self.path('foo') foo_lock = '%s.lock' % foo orig_f = open(foo, 'rb') self.assertEqual(orig_f.read(), b'foo contents') orig_f.close() f = GitFile(foo, 'wb') f.write(b'new contents') f.abort() self.assertTrue(f.closed) self.assertFalse(os.path.exists(foo_lock)) new_orig_f = open(foo, 'rb') self.assertEqual(new_orig_f.read(), b'foo contents') new_orig_f.close()
def _remove_packed_ref(self, name): if self._packed_refs is None: return filename = os.path.join(self.path, b'packed-refs') # reread cached refs from disk, while holding the lock f = GitFile(filename, 'wb') try: self._packed_refs = None self.get_packed_refs() if name not in self._packed_refs: return del self._packed_refs[name] if name in self._peeled_refs: del self._peeled_refs[name] write_packed_refs(f, self._packed_refs, self._peeled_refs) f.close() finally: f.abort()
def set_symbolic_ref(self, name, other, committer=None, timestamp=None, timezone=None, message=None): """Make a ref point at another ref. :param name: Name of the ref to set :param other: Name of the ref to point at :param message: Optional message to describe the change """ self._check_refname(name) self._check_refname(other) filename = self.refpath(name) f = GitFile(filename, 'wb') try: f.write(SYMREF + other + b'\n') sha = self.follow(name)[-1] self._log(name, sha, sha, committer=committer, timestamp=timestamp, timezone=timezone, message=message) except BaseException: f.abort() raise else: f.close()
def _complete_thin_pack(self, f, path, copier, indexer): """Move a specific file containing a pack into the pack directory. :note: The file should be on the same file system as the packs directory. :param f: Open file object for the pack. :param path: Path to the pack file. :param copier: A PackStreamCopier to use for writing pack data. :param indexer: A PackIndexer for indexing the pack. """ entries = list(indexer) # Update the header with the new number of objects. f.seek(0) write_pack_header(f, len(entries) + len(indexer.ext_refs())) # Must flush before reading (http://bugs.python.org/issue3207) f.flush() # Rescan the rest of the pack, computing the SHA with the new header. new_sha = compute_file_sha(f, end_ofs=-20) # Must reposition before writing (http://bugs.python.org/issue3207) f.seek(0, os.SEEK_CUR) # Complete the pack. for ext_sha in indexer.ext_refs(): assert len(ext_sha) == 20 type_num, data = self.get_raw(ext_sha) offset = f.tell() crc32 = write_pack_object(f, type_num, data, sha=new_sha) entries.append((ext_sha, offset, crc32)) pack_sha = new_sha.digest() f.write(pack_sha) f.close() # Move the pack in. entries.sort() pack_base_name = self._get_pack_basepath(entries) target_pack = pack_base_name + '.pack' if sys.platform == 'win32': # Windows might have the target pack file lingering. Attempt # removal, silently passing if the target does not exist. try: os.remove(target_pack) except (IOError, OSError) as e: if e.errno != errno.ENOENT: raise os.rename(path, target_pack) # Write the index. index_file = GitFile(pack_base_name + '.idx', 'wb') try: write_pack_index_v2(index_file, entries, pack_sha) index_file.close() finally: index_file.abort() # Add the pack to the store and return it. final_pack = Pack(pack_base_name) final_pack.check_length_and_checksum() self._add_known_pack(pack_base_name, final_pack) return final_pack