def _create(cls, repo, path, resolve, reference, force, logmsg=None): """internal method used to create a new symbolic reference. If resolve is False, the reference will be taken as is, creating a proper symbolic reference. Otherwise it will be resolved to the corresponding object and a detached symbolic reference will be created instead""" full_ref_path = cls.to_full_path(path) abs_ref_path = join(repo.git_dir, full_ref_path) # figure out target data target = reference if resolve: target = repo.rev_parse(str(reference)) if not force and isfile(abs_ref_path): target_data = str(target) if isinstance(target, SymbolicReference): target_data = target.path if not resolve: target_data = "ref: " + target_data existing_data = open(abs_ref_path, 'rb').read().decode(defenc).strip() if existing_data != target_data: raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path, existing_data, target_data)) # END no force handling ref = cls(repo, full_ref_path) ref.set_reference(target, logmsg) return ref
def is_dirty(self, index=True, working_tree=True, untracked_files=False, submodules=True): """ :return: ``True``, the repository is considered dirty. By default it will react like a git-status without untracked files, hence it is dirty if the index or the working copy have changes.""" if self._bare: # Bare repositories with no associated working directory are # always consired to be clean. return False # start from the one which is fastest to evaluate default_args = ['--abbrev=40', '--full-index', '--raw'] if not submodules: default_args.append('--ignore-submodules') if index: # diff index against HEAD if isfile(self.index.path) and \ len(self.git.diff('--cached', *default_args)): return True # END index handling if working_tree: # diff index against working tree if len(self.git.diff(*default_args)): return True # END working tree handling if untracked_files: if len(self._get_untracked_files(ignore_submodules=not submodules)): return True # END untracked files return False
def is_git_dir(d): """ This is taken from the git setup.c:is_git_directory function.""" if isdir(d) and isdir(join(d, "objects")) and isdir(join(d, "refs")): headref = join(d, "HEAD") return isfile(headref) or (os.path.islink(headref) and os.readlink(headref).startswith("refs")) return False
def _create(cls, repo, path, resolve, reference, force, logmsg=None): """internal method used to create a new symbolic reference. If resolve is False, the reference will be taken as is, creating a proper symbolic reference. Otherwise it will be resolved to the corresponding object and a detached symbolic reference will be created instead""" full_ref_path = cls.to_full_path(path) abs_ref_path = join(repo.git_dir, full_ref_path) # figure out target data target = reference if resolve: target = repo.rev_parse(str(reference)) if not force and isfile(abs_ref_path): target_data = str(target) if isinstance(target, SymbolicReference): target_data = target.path if not resolve: target_data = "ref: " + target_data existing_data = open(abs_ref_path, 'rb').read().decode(defenc).strip() if existing_data != target_data: raise OSError( "Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path, existing_data, target_data)) # END no force handling ref = cls(repo, full_ref_path) ref.set_reference(target, logmsg) return ref
def is_git_dir(d): """ This is taken from the git setup.c:is_git_directory function. @throws WorkTreeRepositoryUnsupported if it sees a worktree directory. It's quite hacky to do that here, but at least clearly indicates that we don't support it. There is the unlikely danger to throw if we see directories which just look like a worktree dir, but are none.""" if isdir(d): if isdir(join(d, 'objects')) and isdir(join(d, 'refs')): headref = join(d, 'HEAD') return isfile(headref) or \ (os.path.islink(headref) and os.readlink(headref).startswith('refs')) elif isfile(join(d, 'gitdir')) and isfile(join(d, 'commondir')) and isfile(join(d, 'gitfile')): raise WorkTreeRepositoryUnsupported(d) return False
def find_git_dir(d): if is_git_dir(d): return d elif isfile(d): with open(d) as fp: content = fp.read().rstrip() if content.startswith('gitdir: '): d = join(dirname(d), content[8:]) return find_git_dir(d) return None
def is_git_dir(d): """ This is taken from the git setup.c:is_git_directory function.""" if isdir(d) and \ isdir(join(d, 'objects')) and \ isdir(join(d, 'refs')): headref = join(d, 'HEAD') return isfile(headref) or \ (os.path.islink(headref) and os.readlink(headref).startswith('refs')) return False
def rename(self, new_path, force=False): """Rename self to a new path :param new_path: Either a simple name or a full path, i.e. new_name or features/new_name. The prefix refs/ is implied for references and will be set as needed. In case this is a symbolic ref, there is no implied prefix :param force: If True, the rename will succeed even if a head with the target name already exists. It will be overwritten in that case :return: self :raise OSError: In case a file at path but a different contents already exists """ new_path = self.to_full_path(new_path) if self.path == new_path: return self new_abs_path = join(self.repo.git_dir, new_path) cur_abs_path = join(self.repo.git_dir, self.path) if isfile(new_abs_path): if not force: # if they point to the same file, its not an error with open(new_abs_path, 'rb') as fd1: f1 = fd1.read().strip() with open(cur_abs_path, 'rb') as fd2: f2 = fd2.read().strip() if f1 != f2: raise OSError("File at path %r already exists" % new_abs_path) # else: we could remove ourselves and use the otherone, but # but clarity we just continue as usual # END not force handling os.remove(new_abs_path) # END handle existing target file dname = dirname(new_abs_path) if not isdir(dname): os.makedirs(dname) # END create directory rename(cur_abs_path, new_abs_path) self.path = new_path return self
def _set_alternates(self, alts): """Sets the alternates :parm alts: is the array of string paths representing the alternates at which git should look for objects, i.e. /home/user/repo/.git/objects :raise NoSuchPathError: :note: The method does not check for the existance of the paths in alts as the caller is responsible.""" alternates_path = join(self.git_dir, 'objects', 'info', 'alternates') if not alts: if isfile(alternates_path): os.remove(alternates_path) else: with open(alternates_path, 'wb') as f: f.write("\n".join(alts).encode(defenc))
def rename(self, new_path, force=False): """Rename self to a new path :param new_path: Either a simple name or a full path, i.e. new_name or features/new_name. The prefix refs/ is implied for references and will be set as needed. In case this is a symbolic ref, there is no implied prefix :param force: If True, the rename will succeed even if a head with the target name already exists. It will be overwritten in that case :return: self :raise OSError: In case a file at path but a different contents already exists """ new_path = self.to_full_path(new_path) if self.path == new_path: return self new_abs_path = join(self.repo.git_dir, new_path) cur_abs_path = join(self.repo.git_dir, self.path) if isfile(new_abs_path): if not force: # if they point to the same file, its not an error if open(new_abs_path, 'rb').read().strip() != open(cur_abs_path, 'rb').read().strip(): raise OSError("File at path %r already exists" % new_abs_path) # else: we could remove ourselves and use the otherone, but # but clarity we just continue as usual # END not force handling os.remove(new_abs_path) # END handle existing target file dname = dirname(new_abs_path) if not isdir(dname): os.makedirs(dname) # END create directory rename(cur_abs_path, new_abs_path) self.path = new_path return self
def _set_alternates(self, alts): """Sets the alternates :parm alts: is the array of string paths representing the alternates at which git should look for objects, i.e. /home/user/repo/.git/objects :raise NoSuchPathError: :note: The method does not check for the existance of the paths in alts as the caller is responsible.""" alternates_path = join(self.git_dir, 'objects', 'info', 'alternates') if not alts: if isfile(alternates_path): os.remove(alternates_path) else: try: f = open(alternates_path, 'wb') f.write("\n".join(alts).encode(defenc)) finally: f.close()
def _set_object(self, ref): """ Set our reference to point to the given ref. It will be converted to a specific hexsha. If the reference does not exist, it will be created. :note: TypeChecking is done by the git command""" # check for existence, touch it if required abs_path = self._abs_path() existed = True if not isfile(abs_path): existed = False open(abs_path, 'wb').write(Object.NULL_HEX_SHA) # END quick create # do it safely by specifying the old value try: self.repo.git.update_ref(self.path, ref, (existed and self._get_object().hexsha) or None) except: if not existed: os.remove(abs_path) # END remove file on error if it didn't exist before raise
def store(self, istream): """note: The sha we produce will be hex by nature""" tmp_path = None writer = self.ostream() if writer is None: # open a tmp file to write the data to fd, tmp_path = tempfile.mkstemp(prefix="obj", dir=self._root_path) if istream.binsha is None: writer = FDCompressedSha1Writer(fd) else: writer = FDStream(fd) # END handle direct stream copies # END handle custom writer try: try: if istream.binsha is not None: # copy as much as possible, the actual uncompressed item size might # be smaller than the compressed version stream_copy(istream.read, writer.write, MAXSIZE, self.stream_chunk_size) else: # write object with header, we have to make a new one write_object( istream.type, istream.size, istream.read, writer.write, chunk_size=self.stream_chunk_size ) # END handle direct stream copies finally: if tmp_path: writer.close() # END assure target stream is closed except: if tmp_path: os.remove(tmp_path) raise # END assure tmpfile removal on error hexsha = None if istream.binsha: hexsha = istream.hexsha else: hexsha = writer.sha(as_hex=True) # END handle sha if tmp_path: obj_path = self.db_path(self.object_path(hexsha)) obj_dir = dirname(obj_path) if not isdir(obj_dir): mkdir(obj_dir) # END handle destination directory # rename onto existing doesn't work on windows if os.name == "nt": if isfile(obj_path): remove(tmp_path) else: rename(tmp_path, obj_path) # end rename only if needed else: rename(tmp_path, obj_path) # END handle win32 # make sure its readable for all ! It started out as rw-- tmp file # but needs to be rwrr chmod(obj_path, self.new_objects_mode) # END handle dry_run istream.binsha = hex_to_bin(hexsha) return istream
def store(self, istream): """note: The sha we produce will be hex by nature""" tmp_path = None writer = self.ostream() if writer is None: # open a tmp file to write the data to fd, tmp_path = tempfile.mkstemp(prefix='obj', dir=self._root_path) if istream.binsha is None: writer = FDCompressedSha1Writer(fd) else: writer = FDStream(fd) # END handle direct stream copies # END handle custom writer try: try: if istream.binsha is not None: # copy as much as possible, the actual uncompressed item size might # be smaller than the compressed version stream_copy(istream.read, writer.write, sys.maxint, self.stream_chunk_size) else: # write object with header, we have to make a new one write_object(istream.type, istream.size, istream.read, writer.write, chunk_size=self.stream_chunk_size) # END handle direct stream copies finally: if tmp_path: writer.close() # END assure target stream is closed except: if tmp_path: os.remove(tmp_path) raise # END assure tmpfile removal on error hexsha = None if istream.binsha: hexsha = istream.hexsha else: hexsha = writer.sha(as_hex=True) # END handle sha if tmp_path: obj_path = self.db_path(self.object_path(hexsha)) obj_dir = dirname(obj_path) if not isdir(obj_dir): mkdir(obj_dir) # END handle destination directory # rename onto existing doesn't work on windows if os.name == 'nt' and isfile(obj_path): remove(obj_path) # END handle win322 rename(tmp_path, obj_path) # make sure its readable for all ! It started out as rw-- tmp file # but needs to be rwrr chmod(obj_path, self.new_objects_mode) # END handle dry_run istream.binsha = hex_to_bin(hexsha) return istream