def get_fetch_info_from_stderr(repo, proc, progress): # skip first line as it is some remote info we are not interested in output = IterableList('name') # lines which are no progress are fetch info lines # this also waits for the command to finish # Skip some progress lines that don't provide relevant information fetch_info_lines = list() for line in digest_process_messages(proc.stderr, progress): if line.startswith('From') or line.startswith('remote: Total'): continue elif line.startswith('warning:'): print >> sys.stderr, line continue elif line.startswith('fatal:'): raise GitCommandError(("Error when fetching: %s" % line,), 2) # END handle special messages fetch_info_lines.append(line) # END for each line # read head information fp = open(join(repo.git_dir, 'FETCH_HEAD'),'r') fetch_head_info = fp.readlines() fp.close() assert len(fetch_info_lines) == len(fetch_head_info) output.extend(CmdFetchInfo._from_line(repo, err_line, fetch_line) for err_line,fetch_line in zip(fetch_info_lines, fetch_head_info)) finalize_process(proc) return output
def _set_daemon_export(self, value): filename = join(self.git_dir, self.DAEMON_EXPORT_FILE) fileexists = os.path.exists(filename) if value and not fileexists: touch(filename) elif not value and fileexists: os.unlink(filename)
def _alternates_path(self): if hasattr(self, 'git_dir'): return join(self.git_dir, 'objects', self.alternates_filepath) elif hasattr(self, 'db_path'): return self.db_path(self.alternates_filepath) else: raise AssertionError("This mixin requires a parent type with either the git_dir property or db_path method")
def _get_ref_info(cls, repo, ref_path): """Return: (sha, target_ref_path) if available, the sha the file at rela_path points to, or None. target_ref_path is the reference we point to, or None""" tokens = None try: fp = open(join(repo.git_dir, ref_path), 'r') value = fp.read().rstrip() fp.close() tokens = value.split(" ") except (OSError, IOError): # Probably we are just packed, find our entry in the packed refs file # NOTE: We are not a symbolic ref if we are in a packed file, as these # are excluded explictly for sha, path in cls._iter_packed_refs(repo): if path != ref_path: continue tokens = (sha, path) break # END for each packed ref # END handle packed refs if tokens is None: raise ValueError("Reference at %r does not exist" % ref_path) # is it a reference ? if tokens[0] == 'ref:': return (None, tokens[1]) # its a commit if cls.re_hexsha_only.match(tokens[0]): return (tokens[0], None) raise ValueError("Failed to parse reference information from %r" % ref_path)
def path(cls, ref): """ :return: string to absolute path at which the reflog of the given ref instance would be found. The path is not guaranteed to point to a valid file though. :param ref: SymbolicReference instance""" return join(ref.repo.git_dir, "logs", to_native_path(ref.path))
def get_fetch_info_from_stderr(repo, proc, progress): # skip first line as it is some remote info we are not interested in output = IterableList('name') # lines which are no progress are fetch info lines # this also waits for the command to finish # Skip some progress lines that don't provide relevant information fetch_info_lines = list() for line in digest_process_messages(proc.stderr, progress): if line.startswith('From') or line.startswith('remote: Total'): continue elif line.startswith('warning:'): print >> sys.stderr, line continue elif line.startswith('fatal:'): raise GitCommandError(("Error when fetching: %s" % line,), 2) # END handle special messages fetch_info_lines.append(line) # END for each line # read head information fp = open(join(repo.git_dir, 'FETCH_HEAD'), 'r') fetch_head_info = fp.readlines() fp.close() assert len(fetch_info_lines) == len(fetch_head_info) output.extend(CmdFetchInfo._from_line(repo, err_line, fetch_line) for err_line, fetch_line in zip(fetch_info_lines, fetch_head_info)) finalize_process(proc) return output
def _alternates_path(self): if hasattr(self, 'git_dir'): return join(self.git_dir, 'objects', self.alternates_filepath) elif hasattr(self, 'db_path'): return self.db_path(self.alternates_filepath) else: raise AssertionError( "This mixin requires a parent type with either the git_dir property or db_path method" )
def delete(cls, repo, path): """Delete the reference at the given path :param repo: Repository to delete the reference from :param path: Short or full path pointing to the reference, i.e. refs/myreference or just "myreference", hence 'refs/' is implied. Alternatively the symbolic reference to be deleted""" full_ref_path = cls.to_full_path(path) abs_path = join(repo.git_dir, full_ref_path) if exists(abs_path): os.remove(abs_path) else: # check packed refs pack_file_path = cls._get_packed_refs_path(repo) try: reader = open(pack_file_path, 'rb') except (OSError, IOError): pass # it didnt exist at all else: new_lines = list() made_change = False dropped_last_line = False for line in reader: # keep line if it is a comment or if the ref to delete is not # in the line # If we deleted the last line and this one is a tag-reference object, # we drop it as well if ( line.startswith('#') or full_ref_path not in line ) and \ (not dropped_last_line or dropped_last_line and not line.startswith('^')): new_lines.append(line) dropped_last_line = False continue # END skip comments and lines without our path # drop this line made_change = True dropped_last_line = True # END for each line in packed refs reader.close() # write the new lines if made_change: # write-binary is required, otherwise windows will # open the file in text mode and change LF to CRLF ! open(pack_file_path, 'wb').writelines(new_lines) # END write out file # END open exception handling # END handle deletion # delete the reflog reflog_path = RefLog.path(cls(repo, full_ref_path)) if os.path.isfile(reflog_path): os.remove(reflog_path)
def delete(cls, repo, path): """Delete the reference at the given path :param repo: Repository to delete the reference from :param path: Short or full path pointing to the reference, i.e. refs/myreference or just "myreference", hence 'refs/' is implied. Alternatively the symbolic reference to be deleted""" full_ref_path = cls.to_full_path(path) abs_path = join(repo.git_dir, full_ref_path) if exists(abs_path): os.remove(abs_path) else: # check packed refs pack_file_path = cls._get_packed_refs_path(repo) try: reader = open(pack_file_path, 'rb') except (OSError, IOError): pass # it didnt exist at all else: new_lines = list() made_change = False dropped_last_line = False for line in reader: # keep line if it is a comment or if the ref to delete is not # in the line # If we deleted the last line and this one is a tag-reference object, # we drop it as well if ( line.startswith('#') or full_ref_path not in line ) and \ ( not dropped_last_line or dropped_last_line and not line.startswith('^') ): new_lines.append(line) dropped_last_line = False continue # END skip comments and lines without our path # drop this line made_change = True dropped_last_line = True # END for each line in packed refs reader.close() # write the new lines if made_change: # write-binary is required, otherwise windows will # open the file in text mode and change LF to CRLF ! open(pack_file_path, 'wb').writelines(new_lines) # END write out file # END open exception handling # END handle deletion # delete the reflog reflog_path = RefLog.path(cls(repo, full_ref_path)) if os.path.isfile(reflog_path): os.remove(reflog_path)
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 _clone(cls, git, url, path, progress, **kwargs): # special handling for windows for path at which the clone should be # created. # tilde '~' will be expanded to the HOME no matter where the ~ occours. Hence # we at least give a proper error instead of letting git fail prev_cwd = None prev_path = None if os.name == 'nt': if '~' in path: raise OSError("Git cannot handle the ~ character in path %r correctly" % path) # on windows, git will think paths like c: are relative and prepend the # current working dir ( before it fails ). We temporarily adjust the working # dir to make this actually work match = re.match("(\w:[/\\\])(.*)", path) if match: prev_cwd = os.getcwd() prev_path = path drive, rest_of_path = match.groups() os.chdir(drive) path = rest_of_path kwargs['with_keep_cwd'] = True # END cwd preparation # END windows handling try: proc = git.clone(url, path, with_extended_output=True, as_process=True, v=True, **add_progress(kwargs, git, progress)) if progress is not None: digest_process_messages(proc.stderr, progress) # END digest progress messages finalize_process(proc) finally: if prev_cwd is not None: os.chdir(prev_cwd) path = prev_path # END reset previous working dir # END bad windows handling # our git command could have a different working dir than our actual # environment, hence we prepend its working dir if required if not os.path.isabs(path) and git.working_dir: path = join(git._working_dir, path) # adjust remotes - there may be operating systems which use backslashes, # These might be given as initial paths, but when handling the config file # that contains the remote from which we were clones, git stops liking it # as it will escape the backslashes. Hence we undo the escaping just to be # sure repo = cls(os.path.abspath(path)) if repo.remotes: repo.remotes[0].config_writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/")) # END handle remote repo return repo
def _clone(cls, git, url, path, progress, **kwargs): # special handling for windows for path at which the clone should be # created. # tilde '~' will be expanded to the HOME no matter where the ~ occours. Hence # we at least give a proper error instead of letting git fail prev_cwd = None prev_path = None if os.name == 'nt': if '~' in path: raise OSError("Git cannot handle the ~ character in path %r correctly" % path) # on windows, git will think paths like c: are relative and prepend the # current working dir ( before it fails ). We temporarily adjust the working # dir to make this actually work match = re.match("(\w:[/\\\])(.*)", path) if match: prev_cwd = os.getcwd() prev_path = path drive, rest_of_path = match.groups() os.chdir(drive) path = rest_of_path kwargs['with_keep_cwd'] = True # END cwd preparation # END windows handling try: proc = git.clone(url, path, with_extended_output=True, as_process=True, v=True, **add_progress(kwargs, git, progress)) if progress is not None: digest_process_messages(proc.stderr, progress) #END digest progress messages finalize_process(proc) finally: if prev_cwd is not None: os.chdir(prev_cwd) path = prev_path # END reset previous working dir # END bad windows handling # our git command could have a different working dir than our actual # environment, hence we prepend its working dir if required if not os.path.isabs(path) and git.working_dir: path = join(git._working_dir, path) # adjust remotes - there may be operating systems which use backslashes, # These might be given as initial paths, but when handling the config file # that contains the remote from which we were clones, git stops liking it # as it will escape the backslashes. Hence we undo the escaping just to be # sure repo = cls(os.path.abspath(path)) if repo.remotes: repo.remotes[0].config_writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/")) # END handle remote repo return repo
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 delete(cls, repo, *refs, **kwargs): """Delete the given remote references. :note: kwargs are given for compatability with the base class method as we should not narrow the signature.""" repo.git.branch("-d", "-r", *refs) # the official deletion method will ignore remote symbolic refs - these # are generally ignored in the refs/ folder. We don't though # and delete remainders manually for ref in refs: try: os.remove(join(repo.git_dir, ref.path)) except OSError: pass
def sanity_check_paths(self, repo_path, paths, path_type): """ Checks existence of a path in the given repo :param repo_path: Relative or absolute path to the top level of the repo :param paths: List of paths to test :param path_type: String to indicate what type of path was being tested in error messages :raises InputException if conditions are not met """ errors = [] for path in paths: full_path = join(repo_path, path) if not os.path.exists(full_path): errors.append("{} path {} does not exist!".format(path_type, full_path)) self.fail_on_errors(errors)
def _path_at_level(self, level ): # we do not support an absolute path of the gitconfig on windows , # use the global config instead if sys.platform == "win32" and level == "system": level = "global" #END handle windows if level == "system": return "/etc/%s" % self.system_config_file_name elif level == "global": return normpath(expanduser("~/.%s" % self.system_config_file_name)) elif level == "repository": return join(self.git_dir, self.repo_config_file_name) #END handle level raise ValueError("Invalid configuration level: %r" % level)
def _path_at_level(self, level): # we do not support an absolute path of the gitconfig on windows , # use the global config instead if sys.platform == "win32" and level == "system": level = "global" # END handle windows if level == "system": return "/etc/%s" % self.system_config_file_name elif level == "global": return normpath(expanduser("~/.%s" % self.system_config_file_name)) elif level == "repository": return join(self.git_dir, self.repo_config_file_name) # END handle level raise ValueError("Invalid configuration level: %r" % level)
def _initialize(self, path): epath = abspath(expandvars(expanduser(path or os.getcwd()))) if not exists(epath): raise NoSuchPathError(epath) #END check file self._working_tree_dir = None self._git_path = None curpath = epath # walk up the path to find the .git dir while curpath: if is_git_dir(curpath): self._git_path = curpath self._working_tree_dir = os.path.dirname(curpath) break gitpath = join(curpath, self.repo_dir) if is_git_dir(gitpath): self._git_path = gitpath self._working_tree_dir = curpath break curpath, dummy = os.path.split(curpath) if not dummy: break # END while curpath if self._git_path is None: raise InvalidGitRepositoryError(epath) # END path not found self._bare = self._working_tree_dir is None if hasattr(self, 'config_reader'): try: self._bare = self.config_reader("repository").getboolean( 'core', 'bare') except Exception: # lets not assume the option exists, although it should pass #END handle exception #END check bare flag self._working_tree_dir = self._bare and None or self._working_tree_dir
def _initialize(self, path): epath = abspath(expandvars(expanduser(path or os.getcwd()))) if not exists(epath): raise NoSuchPathError(epath) #END check file self._working_tree_dir = None self._git_path = None curpath = epath # walk up the path to find the .git dir while curpath: if is_git_dir(curpath): self._git_path = curpath self._working_tree_dir = os.path.dirname(curpath) break gitpath = join(curpath, self.repo_dir) if is_git_dir(gitpath): self._git_path = gitpath self._working_tree_dir = curpath break curpath, dummy = os.path.split(curpath) if not dummy: break # END while curpath if self._git_path is None: raise InvalidGitRepositoryError(epath) # END path not found self._bare = self._working_tree_dir is None if hasattr(self, 'config_reader'): try: self._bare = self.config_reader("repository").getboolean('core','bare') except Exception: # lets not assume the option exists, although it should pass #END handle exception #END check bare flag self._working_tree_dir = self._bare and None or self._working_tree_dir
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: # could just use the resolve method, but it could be expensive # so we handle most common cases ourselves if isinstance(reference, cls.ObjectCls): target = reference.hexsha elif isinstance(reference, SymbolicReference): target = reference.object.hexsha else: target = repo.resolve_object(str(reference)) #END handle resoltion #END need resolution 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().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 _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: # could just use the resolve method, but it could be expensive # so we handle most common cases ourselves if isinstance(reference, cls.ObjectCls): target = reference.hexsha elif isinstance(reference, SymbolicReference): target = reference.object.hexsha else: target = repo.resolve_object(str(reference)) # END handle resoltion # END need resolution 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().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 objects_dir(self): return join(self.git_dir, self.objs_dir)
def db_path(self, rela_path=None): if not rela_path: return self._root_path return join(self._root_path, rela_path)
def _get_daemon_export(self): filename = join(self.git_dir, self.DAEMON_EXPORT_FILE) return os.path.exists(filename)
def _get_packed_refs_path(cls, repo): return join(repo.git_dir, 'packed-refs')
def _set_description(self, descr): filename = join(self.git_dir, 'description') file(filename, 'w').write(descr + '\n')
def _get_description(self): filename = join(self.git_dir, 'description') return file(filename).read().rstrip()
def object_path(self, hexsha): """ :return: path at which the object with the given hexsha would be stored, relative to the database root""" return join(hexsha[:2], hexsha[2:])
def _set_description(self, descr): filename = join(self.git_dir, 'description') file(filename, 'w').write(descr+'\n')