def test_simple_local(self): f1_1 = make_object(Blob, data=b'f1') commit_spec = [[1], [2, 1], [3, 1, 2]] trees = { 1: [(b'f1', f1_1), (b'f2', f1_1)], 2: [(b'f1', f1_1), (b'f2', f1_1)], 3: [(b'f1', f1_1), (b'f2', f1_1)], } c1, c2, c3 = build_commit_graph(self.repo.object_store, commit_spec, trees) self.repo.refs[b"refs/heads/master"] = c3.id self.repo.refs[b"refs/tags/foo"] = c3.id target_path = tempfile.mkdtemp() errstream = BytesIO() self.addCleanup(shutil.rmtree, target_path) r = porcelain.clone(self.repo.path, target_path, checkout=False, errstream=errstream) self.assertEqual(r.path, target_path) target_repo = Repo(target_path) self.assertEqual(target_repo.head(), c3.id) self.assertEqual(c3.id, target_repo.refs[b'refs/tags/foo']) self.assertTrue(b'f1' not in os.listdir(target_path)) self.assertTrue(b'f2' not in os.listdir(target_path)) c = r.get_config() encoded_path = self.repo.path if not isinstance(encoded_path, bytes): encoded_path = encoded_path.encode('utf-8') self.assertEqual(encoded_path, c.get((b'remote', b'origin'), b'url')) self.assertEqual(b'+refs/heads/*:refs/remotes/origin/*', c.get((b'remote', b'origin'), b'fetch'))
def get_git_versions(repos, get_dirty_status=False, verbose=0): """ Returns the repository head guid and dirty status and package version number if installed via pip. The version is only returned if the repo is installed as pip package without edit mode. NOTE: currently the dirty status is not working correctly due to a bug in dulwich... Args: repos ([str]): a list with repositories, e.g. ['qtt', 'qcodes'] get_dirty_status (bool): selects whether to use the dulwich package and collect the local code changes for the repositories. Retuns: r (dict): dictionary with repo names, head guid and (optionally) dirty status for each given repository. """ heads = dict() dirty_stats = dict() for repo in repos: try: package = importlib.import_module(repo) init_location = os.path.split(package.__file__)[0] repo_location = os.path.join(init_location, '..') repository = Repo(repo_location) heads[repo] = repository.head().decode('ascii') if get_dirty_status: status = porcelain.status(repository) is_dirty = len(status.unstaged) == 0 or any( len(item) != 0 for item in status.staged.values()) dirty_stats[repo] = is_dirty except (AttributeError, ModuleNotFoundError, NotGitRepository): heads[repo] = 'none' if get_dirty_status: dirty_stats[repo] = 'none' if verbose: print('{0}: {1}'.format(repo, heads[repo])) return (heads, dirty_stats)
def checkout(repo_path='.'): repo = Repo(repo_path) indexfile = repo.index_path() obj_sto = repo.object_store tree_id = repo[repo.head()].tree build_index_from_tree(repo_path, indexfile, obj_sto, tree_id) return [obj_sto.iter_tree_contents(tree_id)]
def dulwichCommit(self, filePath, fullPath, kind): git = Repo(AUTOGIT_PATH) staged = map(str, [filePath]) git.stage(staged) index = git.open_index() try: committer = git._get_user_identity() except ValueError: committer = "autogit" try: head = git.head() except KeyError: return git.do_commit('%s - autogit commit (via dulwich)' % kind, committer=committer) changes = list( tree_changes(git, index.commit(git.object_store), git['HEAD'].tree)) if changes and len(changes) > 0: return git.do_commit('%s - autogit commit (via dulwich)' % kind, committer=committer) return None
async def info(self, ctx: Context): """ Get information about the bot """ embed = Embed( description= "A utility bot designed just for the Python server! Try `bot.help()` for more info.", url="https://github.com/discord-python/bot") repo = Repo(".") sha = repo[repo.head()].sha().hexdigest() embed.add_field(name="Total Users", value=str(len( self.bot.get_guild(PYTHON_GUILD).members))) embed.add_field(name="Git SHA", value=str(sha)[:7]) embed.set_author(name="Python Bot", url="https://github.com/discord-python/bot", icon_url=BOT_AVATAR_URL) log.info( f"{ctx.author} called bot.about(). Returning information about the bot." ) await ctx.send(embed=embed)
async def about_command(self, ctx: Context): """ Get information about the bot """ embed = Embed( description= "A utility bot designed just for the Python server! Try `!help` for more info.", url="https://gitlab.com/discord-python/projects/bot") repo = Repo(".") sha = repo[repo.head()].sha().hexdigest() embed.add_field(name="Total Users", value=str(len(self.bot.get_guild(Guild.id).members))) embed.add_field(name="Git SHA", value=str(sha)[:7]) embed.set_author(name="Python Bot", url="https://gitlab.com/discord-python/projects/bot", icon_url=URLs.bot_avatar) log.info( f"{ctx.author} called !about. Returning information about the bot." ) await ctx.send(embed=embed)
def test_simple_local(self): f1_1 = make_object(Blob, data=b'f1') commit_spec = [[1], [2, 1], [3, 1, 2]] trees = { 1: [(b'f1', f1_1), (b'f2', f1_1)], 2: [(b'f1', f1_1), (b'f2', f1_1)], 3: [(b'f1', f1_1), (b'f2', f1_1)], } c1, c2, c3 = build_commit_graph(self.repo.object_store, commit_spec, trees) self.repo.refs[b"refs/heads/master"] = c3.id self.repo.refs[b"refs/tags/foo"] = c3.id target_path = tempfile.mkdtemp() errstream = BytesIO() self.addCleanup(shutil.rmtree, target_path) r = porcelain.clone(self.repo.path, target_path, checkout=False, errstream=errstream) self.assertEqual(r.path, target_path) target_repo = Repo(target_path) self.assertEqual(target_repo.head(), c3.id) self.assertEquals(c3.id, target_repo.refs[b'refs/tags/foo']) self.assertTrue(b'f1' not in os.listdir(target_path)) self.assertTrue(b'f2' not in os.listdir(target_path))
def commit_style(repo: Repo, format_rule_name: str) -> Tuple[str, str]: """ Call bash script which commit all changes to `style_name` branch and checkout master back. :param repo: Repo instance to the repository for which style were applied. :param format_rule_name: Applied format rule name. :return: Two commit hashes: where style was applied and where style was disrupt. """ def commit(repo: Repo, msg: str) -> str: """Commit everything.""" for tree_path, entry in repo.open_index().items(): full_path = os.path.join(repo.path.encode(), tree_path) blob = blob_from_path_and_stat(full_path, os.lstat(full_path)) if blob.id != entry.sha: repo.stage(tree_path) return repo.do_commit(msg.encode(), b"Source{d} ML Team <*****@*****.**>") repopath = repo.path base = repo.head() branch_create(repopath, format_rule_name, force=True) update_head(repopath, format_rule_name) style_commit_sha = commit(repo, format_rule_name) build_index_from_tree(repo.path, repo.index_path(), repo.object_store, repo[base].tree) revert_style_commit_sha = commit(repo, "Revert " + format_rule_name) update_head(repopath, b"master") return style_commit_sha.decode(), revert_style_commit_sha.decode()
async def info(self, ctx: Context): """ Get information about the bot """ embed = Embed( description= "A utility bot designed just for the Python server! Try `bot.help()` for more info.", url="https://github.com/discord-python/bot") repo = Repo(".") sha = repo[repo.head()].sha().hexdigest() embed.add_field(name="Total Users", value=str(len( self.bot.get_guild(PYTHON_GUILD).members))) embed.add_field(name="Git SHA", value=str(sha)[:7]) embed.set_author( name="Python Bot", url="https://github.com/discord-python/bot", icon_url= "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle.png" ) await ctx.send(embed=embed)
def test_simple_local(self): f1_1 = make_object(Blob, data=b'f1') commit_spec = [[1], [2, 1], [3, 1, 2]] trees = {1: [(b'f1', f1_1), (b'f2', f1_1)], 2: [(b'f1', f1_1), (b'f2', f1_1)], 3: [(b'f1', f1_1), (b'f2', f1_1)], } c1, c2, c3 = build_commit_graph(self.repo.object_store, commit_spec, trees) self.repo.refs[b"refs/heads/master"] = c3.id self.repo.refs[b"refs/tags/foo"] = c3.id target_path = tempfile.mkdtemp() errstream = BytesIO() self.addCleanup(shutil.rmtree, target_path) r = porcelain.clone(self.repo.path, target_path, checkout=False, errstream=errstream) self.assertEqual(r.path, target_path) target_repo = Repo(target_path) self.assertEqual(target_repo.head(), c3.id) self.assertEqual(c3.id, target_repo.refs[b'refs/tags/foo']) self.assertTrue(b'f1' not in os.listdir(target_path)) self.assertTrue(b'f2' not in os.listdir(target_path)) c = r.get_config() encoded_path = self.repo.path if not isinstance(encoded_path, bytes): encoded_path = encoded_path.encode('utf-8') self.assertEqual(encoded_path, c.get((b'remote', b'origin'), b'url')) self.assertEqual( b'+refs/heads/*:refs/remotes/origin/*', c.get((b'remote', b'origin'), b'fetch'))
def clone_role(url, branch, clone_root_path, clone_folder=None, depth=None): """ Git clone :param url: Source of the git repo :param branch: Branch of the git repo :param clone_root_path: The main folder in which the repo will be cloned. :param clone_folder: The relative folder name of the git clone to the clone_root_path :param depth(str): The git shallow clone depth :returns: latest sha of the clone and its location """ gitcall = ["git", "clone"] if depth and depth.isdigit(): gitcall.extend(["--depth", depth, "--no-single-branch"]) gitcall.append(url) gitcall.extend(["-b", branch]) if not clone_folder: clone_folder = url.split("/")[-1] dirpath = os.path.join(clone_root_path, clone_folder) gitcall.append(dirpath) subprocess.check_call(gitcall) repo = Repo(dirpath) return repo.head(), dirpath
class Git(): """ object that holds the git repository """ def __init__(self): self.repo_path = user_data_dir(appname, appauthor) self.files_under_version_controll = ['config.json', 'data.json'] # initialize repo if it doesn't exist try: self.repo = Repo(self.repo_path) except NotGitRepository: # create repo if not os.path.exists(self.repo_path): try: os.makedirs(self.repo_path) except OSError as exc: # Guard against race condition if exc.errno != errno.EEXIST: raise Repo.init(self.repo_path) self.repo = Repo(self.repo_path) self.commit('initial commit') def commit(self, message): """ commits the current status of files_under_version_controll :param message: str; commit message """ self.repo.stage(self.files_under_version_controll) self.repo.do_commit(str.encode(message), str.encode('nextSongs')) def get_current_head(self): """ get sha as bytes of current head :return: bytes; sha1 checksum of current head """ return self.repo.head() def get_commits(self): """ generates a list of last commits :return: list-of-dulwich.objects.Commit """ commits = [] for i in self.repo.get_walker(): commits.append(i.commit) return reversed( sorted( commits, key=lambda x: datetime.datetime.fromtimestamp(x.author_time))) def restore(self, commit): """ does a hard reset to a given commit :param commit: list-of-dulwich.objects.Commit; commit to reset to """ porcelain.reset(self.repo, 'hard', str.encode(commit.sha().hexdigest())) self.commit("Restored setting and data.") Config.read_config()
def test_submodule(self): temp_dir = tempfile.mkdtemp() repo_dir = os.path.join(os.path.dirname(__file__), "data", "repos") shutil.copytree(os.path.join(repo_dir, "a.git"), os.path.join(temp_dir, "a.git"), symlinks=True) rel = os.path.relpath(os.path.join(repo_dir, "submodule"), temp_dir) os.symlink(os.path.join(rel, "dotgit"), os.path.join(temp_dir, ".git")) r = Repo(temp_dir) self.assertEqual(r.head(), "a90fa2d900a17e99b433217e988c4eb4a2e9a097")
def get_last_commit(self): try: repo = Repo(os.path.dirname(self.path)) except NotGitRepository: repo = Repo.init(os.path.dirname(self.path)) head = repo.head() head_commit = repo.get_object(head) return head_commit
class ArticleList(object): def __init__(self): self.repo = Repo('wiki') self.head = self.repo.get_object(self.repo.head()) self.tree = self.repo.get_object(self.head.tree) def get_article_titles(self): return [a for a in self.tree]
def test_sprouted_tags(self): path, gitsha = self.make_onerev_branch() r = GitRepo(path) r.refs[b"refs/tags/lala"] = r.head() oldrepo = Repository.open(path) revid = oldrepo.get_mapping().revision_id_foreign_to_bzr(gitsha) newbranch = self.clone_git_branch(path, "f") self.assertEqual({"lala": revid}, newbranch.tags.get_tag_dict()) self.assertEqual([revid], newbranch.repository.all_revision_ids())
def test_submodule(self): temp_dir = tempfile.mkdtemp() repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos') shutil.copytree(os.path.join(repo_dir, 'a.git'), os.path.join(temp_dir, 'a.git'), symlinks=True) rel = os.path.relpath(os.path.join(repo_dir, 'submodule'), temp_dir) os.symlink(os.path.join(rel, 'dotgit'), os.path.join(temp_dir, '.git')) r = Repo(temp_dir) self.assertEqual(r.head(), 'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
def _dulwich_commit(self, author, message=DEFAULT_COMMIT_MSG): """ Commit staged files in the repo """ _repo = Repo(self.config['top_dir']) commit_id = _repo.do_commit(message, committer=author) if not _repo.head() == commit_id: raise SartorisError(message=exit_codes[14], exit_code=14)
def get_revision(self): """Read git revision information for the bcfg2 repository""" try: repo = Repo(self.datastore) revision = repo.head() except: logger.error("Failed to read git repository; disabling git support") raise Bcfg2.Server.Plugin.PluginInitError return revision
def git_commit_info(): git = Repo('.') commit = git.get_object(git.head()) return { 'id': commit.id.decode("utf-8")[0:7], 'id_full': commit.id.decode("utf-8"), 'author': regex.findall("(.*?) <(.*?)>", commit.author.decode("utf-8"))[0], 'message': commit.message.decode("utf-8").strip('\r\n').split('\n')[0] }
def get_revision(self): """Read git revision information for the Bcfg2 repository.""" try: repo = Repo(self.datastore) revision = repo.head() except: logger.error( "Failed to read git repository; disabling git support") raise Bcfg2.Server.Plugin.PluginInitError return revision
class Article(object): def __init__(self, title): self.title = title.encode('UTF-8') self.repo = Repo('wiki') self.head = self.repo.get_object(self.repo.head()) self.tree = self.repo.get_object(self.head.tree) try: sha = self.tree[self.title][1] self.content = self.repo[sha].data except KeyError: self.content = u'This space intentionally left blank.' def __str__(self): return self.title def get_content(self): return self.content def get_title(self): return self.title def update_content(self, new_content, author, email, message): new_content = new_content.encode('UTF-8') author = author.encode('UTF-8') message = message.encode('UTF-8') email = email.encode('UTF-8') # create blob, add to existing tree blob = Blob.from_string(new_content) self.tree[self.title] = (0100644, blob.id) # commit commit = Commit() commit.tree = self.tree.id commit.parents = [self.head.id] commit.author = commit.committer = "%s <%s>" % (author, email) commit.commit_time = commit.author_time = int(time()) tz = parse_timezone('+0100')[0] # FIXME: get proper timezone commit.commit_timezone = commit.author_timezone = tz commit.encoding = 'UTF-8' commit.message = message # save everything object_store = self.repo.object_store object_store.add_object(blob) object_store.add_object(self.tree) object_store.add_object(commit) self.repo.refs['refs/heads/master'] = commit.id def update_title(self, new_title): pass
def get_source_revision_from_git(folder: Path) -> str: dot_git: Path = folder / '.git' if not dot_git.is_dir(): raise OSError('Not a Git working copy') repo = Repo(folder) # We truncate the hash string to 7 characters, # that is also the way Git represents in short form. try: return repo.head()[:REVISION_LENGTH].decode() except KeyError: # Folder has just been "git init", no data yet return '-'
def get_hash(self, _id, _user): """ Returns the hashes :param _id: The _id of the repository :return: The hash of the repository """ _result = self.node.find( {"_id": ObjectId(_id)}, _user, _error_prefix_if_not_allowed= "This user doesn't have permissions for this repository.") _repo = Repo(os.path.join(_result["folder"], str(_id))) return _repo.head()
def test_submodule(self): temp_dir = self.mkdtemp() repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos') if isinstance(temp_dir, bytes): temp_dir_str = temp_dir.decode(sys.getfilesystemencoding()) else: temp_dir_str = temp_dir shutil.copytree(os.path.join(repo_dir, 'a.git'), os.path.join(temp_dir_str, 'a.git'), symlinks=True) rel = os.path.relpath(os.path.join(repo_dir, 'submodule'), temp_dir_str) os.symlink(os.path.join(rel, 'dotgit'), os.path.join(temp_dir_str, '.git')) r = Repo(temp_dir) self.assertEqual(r.head(), b'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
def teardown(self): folder = Path.cwd() try: (folder / '.version').unlink() except FileNotFoundError: pass if self.original_revision is None: # Remove the Git repo created in test try: rp = Repo(str(folder)) head = rp[rp.head()] if not len(head.parents): shutil.rmtree(folder / '.git') except NotGitRepository: pass
def _dulwich_reset_to_tag(self, tag=None): """ Resets the HEAD to the commit """ _repo = Repo(self.config['top_dir']) if not tag: sha = _repo.head() else: sha = self._get_commit_sha_for_tag(tag) try: _repo.refs['HEAD'] = sha except AttributeError: raise GitMethodsError(message=exit_codes[7], exit_code=7)
def read_submodule_head(path): """Read the head commit of a submodule. :param path: path to the submodule :return: HEAD sha, None if not a valid head/repository """ from dulwich.errors import NotGitRepository from dulwich.repo import Repo try: repo = Repo(path) except NotGitRepository: return None try: return repo.head() except KeyError: return None
def test_normal_from_repo(self): # Create repo folder = Path.cwd() try: rp = Repo(str(folder)) except NotGitRepository: rp = Repo.init(str(folder)) try: version = rp.head().decode() self.original_revision = version except KeyError: FILE_NAME_TEST = 'file_test.txt' test_file = folder / FILE_NAME_TEST test_file.touch() rp.stage(FILE_NAME_TEST.encode()) version = rp.do_commit(b'Test commit').decode() v = get_source_revision() assert v == version[:10]
def project_detail(request, project_slug): """Show commits for this project.""" repo, project = _get_repo(project_slug) try: repo = Repo(project.path_to_repo) except: return HttpResponse("Invalid repo path %s" % project.path_to_repo) commits = repo.revision_history(repo.head()) branches = repo.refs.as_dict('refs/heads').keys() return render_to_response('project/project_detail.html', { 'project': project, 'commits': commits, 'branches': branches, }, context_instance=RequestContext(request))
def _identify(self,scribblearea): top = self.select.module_name+"."+self.select.currentText() try: import os if (os.getcwd()).endswith('stopeight'): if (self.select.module_name=='stopeight.analyzer.file'): if hasattr(scribblearea,'tablet_id'): sub = str(scribblearea.tablet_id) return (top,sub) else: return (top,'MouseData') else: from dulwich.repo import Repo clibs_repo = Repo('../stopeight/') if (self.select.module_name == ('stopeight_clibs_legacy')) or (self.select.module_name == ('stopeight_clibs_analyzer')): clibs_repo = Repo('../stopeight-clibs/') sub = (clibs_repo.head().decode('utf-8')) return (top,sub) except: return (top)
def read_submodule_head(path): """Read the head commit of a submodule. :param path: path to the submodule :return: HEAD sha, None if not a valid head/repository """ from dulwich.errors import NotGitRepository from dulwich.repo import Repo # Repo currently expects a "str", so decode if necessary. # TODO(jelmer): Perhaps move this into Repo() ? if not isinstance(path, str): path = path.decode(sys.getfilesystemencoding()) try: repo = Repo(path) except NotGitRepository: return None try: return repo.head() except KeyError: return None
def readme(self, date=True, git_commit=False, git_path=".", info=()): """Adds a README.md with useful info. The README consists of the name of the directory (passed in at init time), followed by a bulleted list with various pieces of info. Args: date (bool): Add the date and time in the bulleted list of info. git_commit (bool): Add the current git commit hash in the bulleted list of info. git_path (str or pathlib.Path): The path to the git repo (i.e. a directory that contains `.git`). Only applicable if `git_commit` is True. If the path given is not to a Git repo, a warning is issued, and the Git Commit is replaced with `"(no repo found)"` info (list of str): A list of additional bullets to add in the README. Returns: pathlib.path: Full path to the README. """ readme_path = self.pfile("README.md") with readme_path.open("w") as file: lines = [f"# {self._name}", ""] if date: date_str = self._datetime.strftime("%Y-%m-%d %H:%M:%S") lines.append(f"- Date: {date_str}") if git_commit: git_path = Path(git_path) if (git_path / ".git").exists(): repo = Repo(str(git_path)) commit_hash = repo.head().decode("utf-8") else: warnings.warn("No Git repo found") commit_hash = "(no repo found)" lines.append(f"- Git Commit: {commit_hash}") lines.extend(map(lambda s: f"- {s}", info)) file.write("\n".join(lines) + "\n") return readme_path
def git_prelod(abbr): if not hasattr(settings, "ENABLE_GIT") or not settings.ENABLE_GIT: return global git_active_repo global git_active_commit global git_active_tree global git_old_tree global HEAD gitdir = "%s/%s.git" % (settings.GIT_PATH, abbr) if not os.path.exists(gitdir): git_repo_init(gitdir) git_active_repo = Repo(gitdir) git_active_commit = Commit() HEAD = git_active_repo.head() commit = git_active_repo.commit(HEAD) tree = git_active_repo.tree(commit.tree) git_old_tree = tree.id git_active_tree = tree
def __init__(self, path, init = False): if init : os.makedirs(path) repo = Repo.init_bare(path) else: repo = Repo(path) self._repo = repo self.path = path self.branches = self._getBranches() try: head = repo.head() commitLast = repo.commit(head) self.head = { 'sha' : head, 'tree' : commitLast.tree } except KeyError: self.head = None
def read_submodule_head(path): """Read the head commit of a submodule. Args: path: path to the submodule Returns: HEAD sha, None if not a valid head/repository """ from dulwich.errors import NotGitRepository from dulwich.repo import Repo # Repo currently expects a "str", so decode if necessary. # TODO(user): Perhaps move this into Repo() ? if not isinstance(path, str): path = os.fsdecode(path) try: repo = Repo(path) except NotGitRepository: return None try: return repo.head() except KeyError: return None
def dulwichCommit(self, filePath, fullPath, kind): git = Repo(AUTOGIT_PATH) staged = map(str,[filePath]) git.stage( staged ) index = git.open_index() try: committer = git._get_user_identity() except ValueError: committer = "autogit" try: head = git.head() except KeyError: return git.do_commit( '%s - autogit commit (via dulwich)' % kind, committer=committer) changes = list(tree_changes(git, index.commit(git.object_store), git['HEAD'].tree)) if changes and len(changes) > 0: return git.do_commit( '%s - autogit commit (via dulwich)' % kind, committer=committer) return None
def test_simple_local(self): f1_1 = make_object(Blob, data=b"f1") commit_spec = [[1], [2, 1], [3, 1, 2]] trees = { 1: [(b"f1", f1_1), (b"f2", f1_1)], 2: [(b"f1", f1_1), (b"f2", f1_1)], 3: [(b"f1", f1_1), (b"f2", f1_1)], } c1, c2, c3 = build_commit_graph(self.repo.object_store, commit_spec, trees) self.repo.refs[b"refs/heads/master"] = c3.id self.repo.refs[b"refs/tags/foo"] = c3.id target_path = tempfile.mkdtemp() errstream = BytesIO() self.addCleanup(shutil.rmtree, target_path) r = porcelain.clone(self.repo.path, target_path, checkout=False, errstream=errstream) self.assertEqual(r.path, target_path) target_repo = Repo(target_path) self.assertEqual(target_repo.head(), c3.id) self.assertEqual(c3.id, target_repo.refs[b"refs/tags/foo"]) self.assertTrue(b"f1" not in os.listdir(target_path)) self.assertTrue(b"f2" not in os.listdir(target_path))
class GitAPI(GitAPIBase): """ API for :class:`Git` using :mod:`dulwich` """ def __init__(self, path): GitAPIBase.__init__(self, path) self.repo = Repo(self.path) self.client, self.origin_path = get_transport_and_path( self.repo.get_config().get(("remote", "origin"), "url")) def revision(self): return self.repo.head() def pull(self): try: remote_refs = self.client.fetch( self.origin_path, self.repo, determine_wants=self.repo.object_store.determine_wants_all) except KeyError: etype, err = sys.exc_info()[:2] # try to work around bug # https://bugs.launchpad.net/dulwich/+bug/1025886 try: # pylint: disable=W0212 self.client._fetch_capabilities.remove('thin-pack') # pylint: enable=W0212 except KeyError: raise etype(err) remote_refs = self.client.fetch( self.origin_path, self.repo, determine_wants=self.repo.object_store.determine_wants_all) tree_id = self.repo[remote_refs['HEAD']].tree # iterate over tree content, giving path and blob sha. for entry in self.repo.object_store.iter_tree_contents(tree_id): entry_in_path = entry.in_path(self.repo.path) ensure_dir_exists(os.path.split(entry_in_path.path)[0]) GitFile(entry_in_path.path, 'wb').write(self.repo[entry.sha].data)
def get_sha_and_dirtiness( prompt_on_dirty: bool = True) -> Optional[Tuple[str, bool]]: try: git_status = status() except NotGitRepository: return None dirty = False def to_str(string: Union[str, bytes]) -> str: if isinstance(string, str): return string return string.decode("utf8") def print_files(filenames: Iterable[Union[str, bytes]]) -> None: print("\n".join(f" - {to_str(filename)}" for filename in filenames)) if git_status.untracked: print("Those files are untracked:", file=stderr) print_files(git_status.untracked) dirty = True if git_status.unstaged: print("Those files are unstaged:", file=stderr) print_files(git_status.unstaged) dirty = True if any(git_status.staged.values()): print("Those files are uncommited:", file=stderr) print_files(chain(*git_status.staged.values())) dirty = True if dirty: print("Are you sure you want to continue [y/n]? ", end="") answer = input() if answer != "y": exit(1) repo = Repo(".") sha = to_str(repo.head()) return sha, dirty
class GitStorageHistory(object): def __init__(self, repodir, storage_root_subdir=os.curdir): self.repo = Repo(repodir) self.storage_root_subdir = storage_root_subdir def __getitem__(self, commit_sha): commit = self.repo.commit(commit_sha) tree = self.repo.tree(commit.tree) return GitTreeStorage(self.repo, tree, self.storage_root_subdir) @property def latest_revision(self): return self.repo.head() def eod_revisions(self, max_revision=None): if max_revision is None: max_revision = self.latest_revision results = {} commit_shas = set([max_revision]) while commit_shas: commit_sha = commit_shas.pop() commit = self.repo.commit(commit_sha) commit_date = date_of(commit) commit_info = (time_of(commit), commit_sha) if commit_date in results: results[commit_date] = max(results[commit_date], commit_info) else: results[commit_date] = commit_info commit_shas.update(commit.parents) return dict((date, sha) for (date, (time, sha)) in results.iteritems())
class Gittle(object): """All paths used in Gittle external methods must be paths relative to the git repository """ DEFAULT_COMMIT = "HEAD" DEFAULT_BRANCH = "master" DEFAULT_REMOTE = "origin" DEFAULT_MESSAGE = "**No Message**" DEFAULT_USER_INFO = {"name": None, "email": None} DIFF_FUNCTIONS = { "classic": utils.git.classic_tree_diff, "dict": utils.git.dict_tree_diff, "changes": utils.git.dict_tree_diff, } DEFAULT_DIFF_TYPE = "dict" HIDDEN_REGEXES = [ # Hide git directory r".*\/\.git\/.*" ] # References REFS_BRANCHES = "refs/heads/" REFS_REMOTES = "refs/remotes/" REFS_TAGS = "refs/tags/" # Name pattern truths # Used for detecting if files are : # - deleted # - added # - changed PATTERN_ADDED = (False, True) PATTERN_REMOVED = (True, False) PATTERN_MODIFIED = (True, True) # Permissions MODE_DIRECTORY = 040000 # Used to tell if a tree entry is a directory # Tree depth MAX_TREE_DEPTH = 1000 # Acceptable Root paths ROOT_PATHS = (os.path.curdir, os.path.sep) def __init__(self, repo_or_path, origin_uri=None, auth=None, report_activity=None, *args, **kwargs): if isinstance(repo_or_path, DulwichRepo): self.repo = repo_or_path elif isinstance(repo_or_path, Gittle): self.repo = DulwichRepo(repo_or_path.path) elif isinstance(repo_or_path, basestring): path = os.path.abspath(repo_or_path) self.repo = DulwichRepo(path) else: logging.warning("Repo is of type %s" % type(repo_or_path)) raise Exception("Gittle must be initialized with either a dulwich repository or a string to the path") # Set path self.path = self.repo.path # The remote url self.origin_uri = origin_uri # Report client activty self._report_activity = report_activity # Build ignore filter self.hidden_regexes = copy.copy(self.HIDDEN_REGEXES) self.hidden_regexes.extend(self._get_ignore_regexes()) self.ignore_filter = utils.paths.path_filter_regex(self.hidden_regexes) self.filters = [self.ignore_filter] # Get authenticator if auth: self.authenticator = auth else: self.auth(*args, **kwargs) def report_activity(self, *args, **kwargs): if not self._report_activity: return return self._report_activity(*args, **kwargs) def _format_author(self, name, email): return "%s <%s>" % (name, email) def _format_userinfo(self, userinfo): name = userinfo.get("name") email = userinfo.get("email") if name and email: return self._format_author(name, email) return None def _format_ref(self, base, extra): return "".join([base, extra]) def _format_ref_branch(self, branch_name): return self._format_ref(self.REFS_BRANCHES, branch_name) def _format_ref_remote(self, remote_name): return self._format_ref(self.REFS_REMOTES, remote_name) def _format_ref_tag(self, tag_name): return self._format_ref(self.REFS_TAGS, tag_name) @property def head(self): """Return SHA of the current HEAD """ return self.repo.head() @property def is_bare(self): """Bare repositories have no working directories or indexes """ return self.repo.bare @property def is_working(self): return not (self.is_bare) def has_index(self): """Opposite of is_bare """ return self.repo.has_index() @property def has_commits(self): """ If the repository has no HEAD we consider that is has no commits """ try: self.repo.head() except KeyError: return False return True def ref_walker(self, ref=None): """ Very simple, basic walker """ ref = ref or "HEAD" sha = self._commit_sha(ref) return self.repo.revision_history(sha) def branch_walker(self, branch): branch = branch or self.DEFAULT_BRANCH ref = self._format_ref_branch(branch) return self.ref_walker(ref) def commit_info(self, start=0, end=None, branch=None): """Return a generator of commits with all their attached information """ if not self.has_commits: return [] commits = [utils.git.commit_info(entry) for entry in self.branch_walker(branch)] if not end: return commits return commits[start:end] @funky.uniquify def recent_contributors(self, n=None, branch=None): n = n or 10 return funky.pluck(self.commit_info(end=n, branch=branch), "author") @property def commit_count(self): try: return len(self.ref_walker()) except KeyError: return 0 def commits(self): """Return a list of SHAs for all the concerned commits """ return [commit["sha"] for commit in self.commit_info()] @property def git_dir(self): return self.repo.controldir() def auth(self, *args, **kwargs): self.authenticator = GittleAuth(*args, **kwargs) return self.authenticator # Generate a branch selector (used for pushing) def _wants_branch(self, branch_name=None): branch_name = branch_name or self.DEFAULT_BRANCH refs_key = self._format_ref_branch(branch_name) sha = self.branches[branch_name] def wants_func(old): refs_key = self._format_ref_branch(branch_name) return {refs_key: sha} return wants_func def _get_ignore_regexes(self): gitignore_filename = os.path.join(self.path, ".gitignore") if not os.path.exists(gitignore_filename): return [] lines = open(gitignore_filename).readlines() globers = map(lambda line: line.rstrip(), lines) return utils.paths.globers_to_regex(globers) # Get the absolute path for a file in the git repo def abspath(self, repo_file): return os.path.abspath(os.path.join(self.path, repo_file)) # Get the relative path from the absolute path def relpath(self, abspath): return os.path.relpath(abspath, self.path) @property def last_commit(self): return self[self.repo.head()] @property def index(self): return self.repo.open_index() @classmethod def init(cls, path, bare=None, *args, **kwargs): """Initialize a repository""" mkdir_safe(path) # Constructor to use if bare: constructor = DulwichRepo.init_bare else: constructor = DulwichRepo.init # Create dulwich repo repo = constructor(path) # Create Gittle repo return cls(repo, *args, **kwargs) @classmethod def init_bare(cls, *args, **kwargs): kwargs.setdefault("bare", True) return cls.init(*args, **kwargs) def get_client(self, origin_uri=None, **kwargs): # Get the remote URL origin_uri = origin_uri or self.origin_uri # Fail if inexistant if not origin_uri: raise InvalidRemoteUrl() client_kwargs = {} auth_kwargs = self.authenticator.kwargs() client_kwargs.update(auth_kwargs) client_kwargs.update(kwargs) client_kwargs.update({"report_activity": self.report_activity}) client, remote_path = get_transport_and_path(origin_uri, **client_kwargs) return client, remote_path def push_to(self, origin_uri, branch_name=None, progress=None, progress_stderr=None): selector = self._wants_branch(branch_name=branch_name) client, remote_path = self.get_client(origin_uri, progress_stderr=progress_stderr) return client.send_pack(remote_path, selector, self.repo.object_store.generate_pack_contents, progress=progress) # Like: git push def push(self, origin_uri=None, branch_name=None, progress=None, progress_stderr=None): return self.push_to(origin_uri, branch_name, progress, progress_stderr) # Not recommended at ALL ... !!! def dirty_pull_from(self, origin_uri, branch_name=None): # Remove all previously existing data rmtree(self.path) mkdir_safe(self.path) self.repo = DulwichRepo.init(self.path) # Fetch brand new copy from remote return self.pull_from(origin_uri, branch_name) def pull_from(self, origin_uri, branch_name=None): return self.fetch(origin_uri) # Like: git pull def pull(self, origin_uri=None, branch_name=None): return self.pull_from(origin_uri, branch_name) def fetch_remote(self, origin_uri=None): # Get client client, remote_path = self.get_client(origin_uri=origin_uri) # Fetch data from remote repository remote_refs = client.fetch(remote_path, self.repo) return remote_refs def _setup_fetched_refs(self, refs, origin, bare): remote_tags = utils.git.subrefs(refs, "refs/tags") remote_heads = utils.git.subrefs(refs, "refs/heads") # Filter refs clean_remote_tags = utils.git.clean_refs(remote_tags) clean_remote_heads = utils.git.clean_refs(remote_heads) # Base of new refs heads_base = "refs/remotes/" + origin if bare: heads_base = "refs/heads" # Import branches self.import_refs(heads_base, clean_remote_heads) # Import tags self.import_refs("refs/tags", clean_remote_tags) # Update HEAD self["HEAD"] = refs["HEAD"] def fetch(self, origin_uri=None, bare=None, origin=None): bare = bare or False origin = origin or self.DEFAULT_REMOTE # Remote refs remote_refs = self.fetch_remote(origin_uri) # Update head # Hit repo because head doesn't yet exist so # print("REFS = %s" % remote_refs) # Update refs (branches, tags, HEAD) self._setup_fetched_refs(remote_refs, origin, bare) # Checkout working directories if not bare: self.checkout_all() else: self.update_server_info() @classmethod def clone(cls, origin_uri, local_path, auth=None, mkdir=True, bare=False, *args, **kwargs): """Clone a remote repository""" mkdir_safe(local_path) # Initialize the local repository if bare: local_repo = cls.init_bare(local_path) else: local_repo = cls.init(local_path) repo = cls(local_repo, origin_uri=origin_uri, auth=auth, *args, **kwargs) repo.fetch(bare=bare) # Add origin # TODO return repo @classmethod def clone_bare(cls, *args, **kwargs): """Same as .clone except clones to a bare repository by default """ kwargs.setdefault("bare", True) return cls.clone(*args, **kwargs) def _commit(self, committer=None, author=None, message=None, files=None, tree=None, *args, **kwargs): if not tree: # If no tree then stage files modified_files = files or self.modified_files logging.warning("STAGING : %s" % modified_files) self.add(modified_files) # Messages message = message or self.DEFAULT_MESSAGE author_msg = self._format_userinfo(author) committer_msg = self._format_userinfo(committer) return self.repo.do_commit( message=message, author=author_msg, committer=committer_msg, encoding="UTF-8", tree=tree, *args, **kwargs ) def _tree_from_structure(self, structure): # TODO : Support directories tree = Tree() for file_info in structure: # str only try: data = file_info["data"].encode("ascii") name = file_info["name"].encode("ascii") mode = file_info["mode"] except: # Skip file on encoding errors continue blob = Blob() blob.data = data # Store file's contents self.repo.object_store.add_object(blob) # Add blob entry tree.add(name, mode, blob.id) # Store tree self.repo.object_store.add_object(tree) return tree.id # Like: git commmit -a def commit(self, name=None, email=None, message=None, files=None, *args, **kwargs): user_info = {"name": name, "email": email} return self._commit(committer=user_info, author=user_info, message=message, files=files, *args, **kwargs) def commit_structure(self, name=None, email=None, message=None, structure=None, *args, **kwargs): """Main use is to do commits directly to bare repositories For example doing a first Initial Commit so the repo can be cloned and worked on right away """ if not structure: return tree = self._tree_from_structure(structure) user_info = {"name": name, "email": email} return self._commit(committer=user_info, author=user_info, message=message, tree=tree, *args, **kwargs) # Push all local commits # and pull all remote commits def sync(self, origin_uri=None): self.push(origin_uri) return self.pull(origin_uri) def lookup_entry(self, relpath, trackable_files=set()): if not relpath in trackable_files: raise KeyError abspath = self.abspath(relpath) with open(abspath, "rb") as git_file: data = git_file.read() s = sha1() s.update("blob %u\0" % len(data)) s.update(data) return (s.hexdigest(), os.stat(abspath).st_mode) @property @funky.transform(set) def tracked_files(self): return list(self.index) @property @funky.transform(set) def raw_files(self): return utils.paths.subpaths(self.path) @property @funky.transform(set) def ignored_files(self): return utils.paths.subpaths(self.path, filters=self.filters) @property @funky.transform(set) def trackable_files(self): return self.raw_files - self.ignored_files @property @funky.transform(set) def untracked_files(self): return self.trackable_files - self.tracked_files """ @property @funky.transform(set) def modified_staged_files(self): "Checks if the file has changed since last commit" timestamp = self.last_commit.commit_time index = self.index return [ f for f in self.tracked_files if index[f][1][0] > timestamp ] """ # Return a list of tuples # representing the changed elements in the git tree def _changed_entries(self, ref=None): ref = ref or self.DEFAULT_COMMIT if not self.has_commits: return [] obj_sto = self.repo.object_store tree_id = self[ref].tree names = self.trackable_files lookup_func = partial(self.lookup_entry, trackable_files=names) # Format = [((old_name, new_name), (old_mode, new_mode), (old_sha, new_sha)), ...] tree_diff = changes_from_tree(names, lookup_func, obj_sto, tree_id, want_unchanged=False) return list(tree_diff) @funky.transform(set) def _changed_entries_by_pattern(self, pattern): changed_entries = self._changed_entries() filtered_paths = [ funky.first_true(names) for names, modes, sha in changed_entries if tuple(map(bool, names)) == pattern and funky.first_true(names) ] return filtered_paths @property @funky.transform(set) def removed_files(self): return self._changed_entries_by_pattern(self.PATTERN_REMOVED) - self.ignored_files @property @funky.transform(set) def added_files(self): return self._changed_entries_by_pattern(self.PATTERN_ADDED) - self.ignored_files @property @funky.transform(set) def modified_files(self): modified_files = self._changed_entries_by_pattern(self.PATTERN_MODIFIED) - self.ignored_files return modified_files @property @funky.transform(set) def modified_unstaged_files(self): timestamp = self.last_commit.commit_time return [f for f in self.tracked_files if os.stat(self.abspath(f)).st_mtime > timestamp] @property def pending_files(self): """ Returns a list of all files that could be possibly staged """ # Union of both return self.modified_files | self.added_files | self.removed_files @property def pending_files_by_state(self): files = {"modified": self.modified_files, "added": self.added_files, "removed": self.removed_files} # "Flip" the dictionary return {path: state for state, paths in files.items() for path in paths} """ @property @funky.transform(set) def modified_files(self): return self.modified_staged_files | self.modified_unstaged_files """ # Like: git add @funky.arglist_method def stage(self, files): return self.repo.stage(files) def add(self, *args, **kwargs): return self.stage(*args, **kwargs) # Like: git rm @funky.arglist_method def rm(self, files, force=False): index = self.index index_files = filter(lambda f: f in index, files) for f in index_files: del self.index[f] return index.write() def mv_fs(self, file_pair): old_name, new_name = file_pair os.rename(old_name, new_name) # Like: git mv @funky.arglist_method def mv(self, files_pair): index = self.index files_in_index = filter(lambda f: f[0] in index, files_pair) map(self.mv_fs, files_in_index) old_files = map(funky.first, files_in_index) new_files = map(funky.last, files_in_index) self.add(new_files) self.rm(old_files) self.add(old_files) return @working_only def _checkout_tree(self, tree): return build_index_from_tree(self.repo.path, self.repo.index_path(), self.repo.object_store, tree) def checkout_all(self, commit_sha=None): commit_sha = commit_sha or self.head commit_tree = self._commit_tree(commit_sha) # Rebuild index from the current tree return self._checkout_tree(commit_tree) def checkout(self, commit_sha=None, files=None): """Checkout only a select amount of files """ commit_sha = commit_sha or self.head files = files or [] return self @funky.arglist_method def reset(self, files, commit="HEAD"): pass def rm_all(self): self.index.clear() return self.index.write() def _to_commit(self, commit_obj): """Allows methods to accept both SHA's or dulwich Commit objects as arguments """ if isinstance(commit_obj, basestring): return self.repo[commit_obj] return commit_obj def _commit_sha(self, commit_obj): """Extracts a Dulwich commits SHA """ if utils.git.is_sha(commit_obj): return commit_obj elif isinstance(commit_obj, basestring): # Can't use self[commit_obj] to avoid infinite recursion commit_obj = self.repo[commit_obj] return commit_obj.id def _blob_data(self, sha): """Return a blobs content for a given SHA """ return self[sha].data # Get the nth parent back for a given commit def get_parent_commit(self, commit, n=None): """ Recursively gets the nth parent for a given commit Warning: Remember that parents aren't the previous commits """ if n is None: n = 1 commit = self._to_commit(commit) parents = commit.parents if n <= 0 or not parents: # Return a SHA return self._commit_sha(commit) parent_sha = parents[0] parent = self[parent_sha] # Recur return self.get_parent_commit(parent, n - 1) def get_previous_commit(self, commit_ref, n=None): commit_sha = self._parse_reference(commit_ref) n = n or 1 commits = self.commits() return funky.next(commits, commit_sha, n=n, default=commit_sha) def _parse_reference(self, ref_string): # COMMIT_REF~x if "~" in ref_string: ref, count = ref_string.split("~") count = int(count) commit_sha = self._commit_sha(ref) return self.get_previous_commit(commit_sha, count) return self._commit_sha(ref_string) def _commit_tree(self, commit_sha): """Return the tree object for a given commit """ return self[commit_sha].tree def diff(self, commit_sha, compare_to=None, diff_type=None, filter_binary=True): diff_type = diff_type or self.DEFAULT_DIFF_TYPE diff_func = self.DIFF_FUNCTIONS[diff_type] if not compare_to: compare_to = self.get_previous_commit(commit_sha) return self._diff_between(compare_to, commit_sha, diff_function=diff_func) def diff_working(self, ref=None, filter_binary=True): """Diff between the current working directory and the HEAD """ return utils.git.diff_changes_paths( self.repo.object_store, self.path, self._changed_entries(ref=ref), filter_binary=filter_binary ) def get_commit_files(self, commit_sha, parent_path=None, is_tree=None, paths=None): """Returns a dict of the following Format : { "directory/filename.txt": { 'name': 'filename.txt', 'path': "directory/filename.txt", "sha": "xxxxxxxxxxxxxxxxxxxx", "data": "blablabla", "mode": 0xxxxx", }, ... } """ # Default values context = {} is_tree = is_tree or False parent_path = parent_path or "" if is_tree: tree = self[commit_sha] else: tree = self[self._commit_tree(commit_sha)] for mode, path, sha in tree.entries(): # Check if entry is a directory if mode == self.MODE_DIRECTORY: context.update( self.get_commit_files(sha, parent_path=os.path.join(parent_path, path), is_tree=True, paths=paths) ) continue subpath = os.path.join(parent_path, path) # Only add the files we want if not (paths is None or subpath in paths): continue # Add file entry context[subpath] = {"name": path, "path": subpath, "mode": mode, "sha": sha, "data": self._blob_data(sha)} return context def file_versions(self, path): """Returns all commits where given file was modified """ versions = [] commits_info = self.commit_info() seen_shas = set() for commit in commits_info: try: files = self.get_commit_files(commit["sha"], paths=[path]) file_path, file_data = files.items()[0] except IndexError: continue file_sha = file_data["sha"] if file_sha in seen_shas: continue else: seen_shas.add(file_sha) # Add file info commit["file"] = file_data versions.append(file_data) return versions def _diff_between(self, old_commit_sha, new_commit_sha, diff_function=None, filter_binary=True): """Internal method for getting a diff between two commits Please use .diff method unless you have very speciic needs """ # If commit is first commit (new_commit_sha == old_commit_sha) # then compare to an empty tree if new_commit_sha == old_commit_sha: old_tree = Tree() else: old_tree = self._commit_tree(old_commit_sha) new_tree = self._commit_tree(new_commit_sha) return diff_function(self.repo.object_store, old_tree, new_tree, filter_binary=filter_binary) def changes(self, *args, **kwargs): """ List of changes between two SHAs Returns a list of lists of tuples : [ [ (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) ], ... ] """ kwargs["diff_type"] = "changes" return self.diff(*args, **kwargs) def changes_count(self, *args, **kwargs): return len(self.changes(*args, **kwargs)) def _refs_by_pattern(self, pattern): refs = self.refs def item_filter(key_value): """Filter only concered refs""" key, value = key_value return key.startswith(pattern) def item_map(key_value): """Rewrite keys""" key, value = key_value new_key = key[len(pattern) :] return (new_key, value) return dict(map(item_map, filter(item_filter, refs.items()))) @property def refs(self): return self.repo.get_refs() def set_refs(refs_dict): for k, v in refs_dict.items(): self.repo[k] = v def import_refs(self, base, other): return self.repo.refs.import_refs(base, other) @property def branches(self): return self._refs_by_pattern(self.REFS_BRANCHES) def _active_branch(self, refs=None, head=None): head = head or self.head refs = refs or self.branches try: return {branch: branch_head for branch, branch_head in refs.items() if branch_head == head}.items()[0] except IndexError: pass return (None, None) @property def active_branch(self): return self._active_branch()[0] @property def active_sha(self): return self._active_branch()[1] @property def remote_branches(self): return self._refs_by_pattern(self.REFS_REMOTES) @property def tags(self): return self._refs_by_pattern(self.REFS_TAGS) @property def remotes(self): """ Dict of remotes { 'origin': 'http://friendco.de/some_user/repo.git', ... } """ config = self.repo.get_config() return {keys[1]: values["url"] for keys, values in config.items() if keys[0] == "remote"} def add_ref(self, new_ref, old_ref): self.repo.refs[new_ref] = self.repo.refs[old_ref] self.update_server_info() def remove_ref(self, ref_name): # Returns False if ref doesn't exist if not ref_name in self.repo.refs: return False del self.repo.refs[ref_name] self.update_server_info() return True def create_branch(self, base_branch, new_branch, tracking=None): """Try creating a new branch which tracks the given remote if such a branch does not exist then branch off a local branch """ # The remote to track tracking = self.DEFAULT_REMOTE # Already exists if new_branch in self.branches: raise Exception("branch %s already exists" % new_branch) # Get information about remote_branch remote_branch = os.path.sep.join([tracking, base_branch]) # Fork Local if base_branch in self.branches: base_ref = self._format_ref_branch(base_branch) # Fork remote elif remote_branch in self.remote_branches: base_ref = self._format_ref_remote(remote_branch) # TODO : track else: raise Exception( "Can not find the branch named '%s' to fork either locally or in '%s'" % (base_branch, tracking) ) # Reference of new branch new_ref = self._format_ref_branch(new_branch) # Copy reference to create branch self.add_ref(new_ref, base_ref) return new_ref def remove_branch(self, branch_name): ref = self._format_ref_branch(branch_name) return self.remove_ref(ref) def switch_branch(self, branch_name, tracking=None, create=None): """Changes the current branch """ if create is None: create = True # Check if branch exists if not branch_name in self.branches: self.create_branch(branch_name, branch_name, tracking=tracking) # Get branch reference branch_ref = self._format_ref_branch(branch_name) # Change main branch self.repo.refs.set_symbolic_ref("HEAD", branch_ref) if self.is_working: # Remove all files self.clean_working() # Add files for the current branch self.checkout_all() def clean(self, force=None, directories=None): untracked_files = self.untracked_files map(os.remove, untracked_files) return untracked_files def clean_working(self): """Purges all the working (removes everything except .git) used by checkout_all to get clean branch switching """ return self.clean() def _get_fs_structure(self, tree_sha, depth=None, parent_sha=None): tree = self[tree_sha] structure = {} if depth is None: depth = self.MAX_TREE_DEPTH elif depth == 0: return structure for mode, path, sha in tree.entries(): # tree if mode == self.MODE_DIRECTORY: # Recur structure[path] = self._get_fs_structure(sha, depth=depth - 1, parent_sha=tree_sha) # commit else: structure[path] = sha structure["."] = tree_sha structure[".."] = parent_sha or tree_sha return structure def _get_fs_structure_by_path(self, tree_sha, path): parts = path.split(os.path.sep) depth = len(parts) + 1 structure = self._get_fs_structure(tree_sha, depth=depth) return funky.subkey(structure, parts) def commit_ls(self, ref, subpath=None): """List a "directory" for a given commit using the tree of thqt commit """ tree_sha = self._commit_tree(ref) # Root path if subpath in self.ROOT_PATHS or not subpath: return self._get_fs_structure(tree_sha, depth=1) # Any other path return self._get_fs_structure_by_path(tree_sha, subpath) def commit_file(self, ref, path): """Return info on a given file for a given commit """ name, info = self.get_commit_files(ref, paths=[path]).items()[0] return info def commit_tree(self, ref, *args, **kwargs): tree_sha = self._commit_tree(ref) return self._get_fs_structure(tree_sha, *args, **kwargs) def update_server_info(self): if not self.is_bare: return update_server_info(self.repo) def _is_fast_forward(self): pass def _merge_fast_forward(self): pass def __hash__(self): """This is required otherwise the memoize function will just mess it up """ return hash(self.path) def __getitem__(self, key): sha = self._parse_reference(key) return self.repo[sha] def __setitem__(self, key, value): self.repo[key] = value # Alias to clone_bare fork = clone_bare log = commit_info diff_count = changes_count comtributors = recent_contributors
#!/usr/bin/env python2 import os.path import urlparse from email.utils import formatdate from dulwich.repo import Repo from dulwich.objects import Blob, Tree, Commit from docutils import io, nodes from docutils.core import publish_doctree, publish_from_doctree from render import MyWriter repo = Repo(".") commit_sha = repo.head() commit = repo.get_object(commit_sha) index = repo.open_index() assert not list(index.changes_from_tree(repo.object_store, commit.tree)), "uncommited changes" store = repo.object_store def render_rst(blob, path): doc = publish_doctree(blob.as_raw_string()) for node in doc.traverse(nodes.reference): uri = urlparse.urlparse(node['refuri']) if not uri.netloc and os.path.basename(uri.path) == "README.rst": node['refuri'] = urlparse.urlunparse( (uri.scheme, uri.netloc, uri.path[:-10] or "./", uri.params, uri.query, uri.fragment)) output = publish_from_doctree( doc, destination_path=path,
def get_version(cls): from dulwich.repo import Repo repo = Repo(cls.src_path) return cls.version + "git" + repo.head()
class Repo(object): """ Wrapper around a libgit Repository that knows: * How to get all the files in the repository * How to get the oid of HEAD * How to get the commit times of the files we want commit times for It's written with speed in mind, given the constraints of making performant code in python! """ def __init__(self, root_folder): self.git = Repository(root_folder) def all_files(self): """Return a set of all the files under git control""" return set([entry.decode() for entry, _ in self.git.open_index().items()]) @property def first_commit(self): """Return the oid of HEAD""" return self.git.head().decode() def file_commit_times(self, use_files_paths, debug=False): """ Traverse the commits in the repository, starting from HEAD until we have found the commit times for all the files we care about. Yield each file once, only when it is found to be changed in some commit. If self.debug is true, also output log.debug for the speed we are going through commits (output commits/second every 1000 commits and every 100000 commits) """ prefixes = PrefixTree() prefixes.fill(use_files_paths) for entry in self.git.get_walker(): # Commit time taking into account the timezone commit_time = entry.commit.commit_time - entry.commit.commit_timezone # Get us the two different tree structures between parents and current cf_and_pf, changes = self.tree_structures_for(() , entry.commit.tree , [self.git.get_object(oid).tree for oid in entry.commit.parents] , prefixes ) # Deep dive into any differences difference = [] if changes: cfs_and_pfs = [(cf_and_pf, changes)] while cfs_and_pfs: nxt, changes = cfs_and_pfs.pop(0) for thing, changes, is_path in self.differences_between(nxt[0], nxt[1], changes, prefixes): if is_path: found = prefixes.remove(thing[:-1], thing[-1]) if found: difference.append('/'.join(thing)) else: cfs_and_pfs.append((thing, changes)) # Only yield if there was a difference if difference: yield entry.commit.sha().hexdigest(), commit_time, difference # If nothing remains, then break! if not prefixes: break def entries_in_tree_oid(self, prefix, tree_oid): """Find the tree at this oid and return entries prefixed with ``prefix``""" try: tree = self.git.get_object(tree_oid) except KeyError: log.warning("Couldn't find object {0}".format(tree_oid)) return empty else: return frozenset(self.entries_in_tree(prefix, tree)) def entries_in_tree(self, prefix, tree): """ Traverse the entries in this tree and yield (prefix, is_tree, oid) Where prefix is a tuple of the given prefix and the name of the entry. """ for entry in tree.items(): if prefix: new_prefix = prefix + (entry.path.decode(), ) else: new_prefix = (entry.path.decode(), ) yield (new_prefix, stat.S_ISDIR(entry.mode), entry.sha) def tree_structures_for(self, prefix, current_oid, parent_oids, prefixes): """ Return the entries for this commit, the entries of the parent commits, and the difference between the two (current_files - parent_files) """ if prefix and prefixes and prefix not in prefixes: return empty, empty parent_files = set() for oid in parent_oids: parent_files.update(self.entries_in_tree_oid(prefix, oid)) current_files = self.entries_in_tree_oid(prefix, current_oid) return (current_files, parent_files), (current_files - parent_files) def differences_between(self, current_files, parent_files, changes, prefixes): """ yield (thing, changes, is_path) If is_path is true, changes is None and thing is the path as a tuple. If is_path is false, thing is the current_files and parent_files for that changed treeentry and changes is the difference between current_files and parent_files. The code here is written to squeeze as much performance as possible out of this operation. """ parent_oid = None if any(is_tree for _, is_tree, _ in changes): if len(changes) == 1: wanted_path = list(changes)[0][0] parent_oid = frozenset([oid for path, is_tree, oid in parent_files if path == wanted_path and is_tree]) else: parent_values = defaultdict(set) parent_changes = parent_files - current_files for path, is_tree, oid in parent_changes: if is_tree: parent_values[path].add(oid) for path, is_tree, oid in changes: if is_tree and path not in prefixes: continue if not is_tree: yield path, None, True else: parent_oids = parent_oid if parent_oid is not None else parent_values.get(path, empty) cf_and_pf, changes = self.tree_structures_for(path, oid, parent_oids, prefixes) if changes: yield cf_and_pf, changes, False
def git_commit_info(): git = Repo('.') commit = git.get_object(git.head()) return {'id': commit.id.decode("utf-8")[0:7], 'id_full': commit.id.decode("utf-8"), 'author': regex.findall("(.*?) <(.*?)>", commit.author.decode("utf-8"))[0], 'message': commit.message.decode("utf-8").strip('\r\n').split('\n')[0]}
def clone( cls, url: str, name: str | None = None, branch: str | None = None, tag: str | None = None, revision: str | None = None, source_root: Path | None = None, clean: bool = False, ) -> Repo: source_root = source_root or cls.get_default_source_root() source_root.mkdir(parents=True, exist_ok=True) name = name or cls.get_name_from_source_url(url=url) target = source_root / name refspec = GitRefSpec(branch=branch, revision=revision, tag=tag) if target.exists(): if clean: # force clean the local copy if it exists, do not reuse remove_directory(target, force=True) else: # check if the current local copy matches the requested ref spec try: current_repo = Repo(str(target)) # type: ignore[no-untyped-call] with current_repo: current_sha = current_repo.head().decode("utf-8") except (NotGitRepository, AssertionError, KeyError): # something is wrong with the current checkout, clean it remove_directory(target, force=True) else: if not is_revision_sha(revision=current_sha): # head is not a sha, this will cause issues later, lets reset remove_directory(target, force=True) elif ( refspec.is_sha and refspec.revision is not None and current_sha.startswith(refspec.revision) ): # if revision is used short-circuit remote fetch head matches return current_repo try: if not cls.is_using_legacy_client(): local = cls._clone(url=url, refspec=refspec, target=target) cls._clone_submodules(repo=local) return local except HTTPUnauthorized: # we do this here to handle http authenticated repositories as dulwich # does not currently support using credentials from git-credential helpers. # upstream issue: https://github.com/jelmer/dulwich/issues/873 # # this is a little inefficient, however preferred as this is transparent # without additional configuration or changes for existing projects that # use http basic auth credentials. logger.debug( "Unable to fetch from private repository '%s', falling back to" " system git", url, ) # fallback to legacy git client return cls._clone_legacy(url=url, refspec=refspec, target=target)
if n == 0: break return ''.join(reversed(s)) CACHE_PREFIX = num_encode(os.getpid()) + '.' CACHE_PREFIX += '.'.join(str(num_encode(int(p))) for p in str(time.time()).split('.')) try: BACKLOG_ARCHIVE = settings.BACKLOG_ARCHIVE if not os.path.exists(os.path.join(BACKLOG_ARCHIVE, '.git')): BACKLOG_ARCHIVE = None except AttributeError: BACKLOG_ARCHIVE = None except ImportError: BACKLOG_ARCHIVE = None RELEASE = None __projectdir__ = os.path.abspath(os.path.dirname(__file__)) while __projectdir__ != '/' and not os.path.exists(os.path.join(__projectdir__, 'settings.py')): __projectdir__ = os.path.dirname(__projectdir__) if __projectdir__ != '/' and os.path.exists(os.path.join(__projectdir__, '.git')): repo = Repo(__projectdir__) for commit in repo.revision_history(repo.head()): RELEASE = 'Git: ' + commit.tree break if RELEASE is None: try: import agilitorelease RELEASE = agilitorelease.RELEASE except: pass
class GitStorage(): def _ignoreFile(self, dirName, fileName): """ used for the copTree stuff ``dirName`` the working directory ``fileName`` list of files inside the directory (dirName) """ result = [] for i in fileName: path = dirName + i if path not in fileToIgnore: result.append(path) return result def _commit(self, tree): """ commit a tree used only by the init ``tree`` tree to commit """ commit = Commit() commit.tree = tree.id commit.encoding = "UTF-8" commit.committer = commit.author = 'debexpo <%s>' % (pylons.config['debexpo.email']) commit.commit_time = commit.author_time = int(time()) tz = parse_timezone('-0200')[0] commit.commit_timezone = commit.author_timezone = tz commit.message = " " self.repo.object_store.add_object(tree) self.repo.object_store.add_object(commit) self.repo.refs["HEAD"] = commit.id log.debug('commiting') return commit.id def __init__(self, path): #creating the repository if os.path.isdir(path): log.debug("directory exist, taking it as a git repository") self.repo = Repo(path) else: log.debug("directory doesn't exist, creating") os.makedirs(path) log.debug("initiate the repo") self.repo = Repo.init(path) log.debug("adding an empty tree to the repository") self._commit(Tree()) #only this function will be used on upload def change(self, files): """ used to change afile in the git storage can be called for the first upload we don't care ``files`` a list of file to change """ if len(files) == 0: log.debug("trying to change nothing will do... nothing") else: log.debug("this will change %i files" % (len(files))) for f in files: self.repo.stage(str(f)) log.debug("stages dones") self.repo.do_commit("this is so awesome that nobody will never see it", committer="same here <*****@*****.**>") def buildTreeDiff(self, dest, tree=None, originalTree=None): """ creating files from the diff between 2 trees, it will be used in the code browser to get older version (walking on history) ``tree`` the tree that you want to compare to ``dest`` the destination folder to build sources ``originalTree`` the original Tree, by default it's the last one by default it's retun the last changed files """ if tree is None: head = self.repo.commit(self.repo.commit(self.repo.head()).parents[0]) tree = self.repo.tree(head.tree) if originalTree is None: originalTree = self.repo.tree(self.repo.commit(self.repo.head()).tree) blobToBuild = [] #getting blob that have changed for blob in self.repo.object_store.iter_tree_contents(tree.id): if blob not in originalTree: blobToBuild.append(blob) fileToIgnore.append(blob.path) repoLocation = os.path.join(str(self.repo).split("'")[1]) #creating the folder with link to older files if os.path.exists(repoLocation + dest): log.warning("%s already exist, copy will not work") else: log.debug("copying files") shutil.copytree(repoLocation, repoLocation + dest, symlinks=True, ignore=self._ignoreFile) for b in blobToBuild: fileDirectory = os.path.split(b.path) fileDirectory.pop() if not os.path.exists(os.path.join(repoLocation + dest, os.path.join(fileDirectory))): os.makedirs(os.path.join(repoLocation + dest, os.path.join(fileDirectory))) file = open(os.path.join(repoLocation + dest, b.path), 'w') file.write(self.repo.get_object(b.sha).as_raw_string()) file.close() tree = None originalTree = None #get* def getLastTree(self): """ return the last tree """ return self.repo.tree(self.repo._commit(self.repo.head()).tree) def getAllTrees(self): """ return trees """ result = [] commit = self.repo._commit(self.repo.head()) for c in commit._get_parents(): result.append(c.tree) return result def getOlderFileContent(self, file): """ return the first file's content that changed from the file ``file`` the file to work on """ with open(file) as f: originalBlob = Blob.from_string("".join(f.readlines())) trees = self.getAllTrees() for t in trees: #parsing tree in order to find the tree where the file change if originalBlob not in t: tree = t break #tree must be existent, other way file is not correct if tree is None: log.error( "there is no tree that contain this blob this souldn't happen, other way this file does not appear to come from this package") else: if self.repo._commit(self.repo.head()).tree == tree: olderTree = self.repo.commit(self.repo.head())._get_parents()[0].tree else: for c in self.repo._commit(self.repo.head())._get_parents(): if c.tree == tree: try: olderTree = c.get_parents()[0] except IndexError: log.debug("file is the last version") olderTree = tree if olderTree != tree: #we must check here the blob that contains the older file for b in self.repo.object_store.iter_tree_contents(olderTree.id): if originalBlob.path == b.path: #older blob find! awesome, in the first loop we already test if they are the same # that's why we can now return the content of the file return self.repo.get_object(b.sha).as_raw_string() return "" def getOlderCommits(self): """ return a list of all commits """ return self.repo.commit(self.repo.head())._get_parents()
# -*- coding: utf-8 -*- from distutils.core import setup try: from dulwich.repo import Repo repo = Repo('.') version = repo.head() except: version = 'unknown' datafiles = [] setup(name='vcsstats', version=version, author='Henrik Stuart, Alexander Færøy', author_email='*****@*****.**', url='http://github.com/ahf/vcsstats/tree/master', description='Version control system statistics', license='MIT', scripts=[], data_files=datafiles, packages=['vcsstatslib', 'vcsstatslib.charts'] )
def edit(self, content=None, username=None, *virtual_path, **keywords): '''id: the git id of the blob before it was edited branch: master (default)''' #setup the defaults branch = "master" url = ManagedPath(virtual_path) if "branch" in keywords: branch = keywords["branch"] sha = self.sha print "sha is: ", sha print "keywords: ", keywords commit = sha print "content is: ", content print "self.filename is: ", self.filename if content is None: repo = Repo(self.package.path()) set_head(repo, branch) if not sha: print "repo.head() = ", repo.head() sha = dulwich.object_store.tree_lookup_path(repo.get_object, repo.get_object(repo.head()).tree, self.filename)[1] obj = repo.get_object(sha) contents = obj.as_pretty_string() return_contents = "<form action=\"" + cherrypy.url() + "\" method=\"POST\">" return_contents = return_contents + "<textarea name=content rows='20' cols='120'>" + contents + "</textarea><br />" #if the user isn't logged in ... if not hasattr(cherrypy.session, "login"): return_contents = return_contents + "username: <input type=text name=username value=\"anonymous\"><br />" if sha: return_contents = return_contents + "<input type=hidden name=id value=\"" + sha + "\">" return_contents = return_contents + "<input type=hidden name=\"branch\" value=\"" + branch + "\">" return_contents = return_contents + "<input type=submit name=submit value=edit></form>" self.content = return_contents self.branch = branch return self.respond() elif (sha or branch): #it's been edited if username==None and hasattr(cherrypy.session, "login"): if cherrypy.session.login==None: raise ValueError, "FileViewer.edit: no username supplied" elif username==None or username=="anonymous": anon = True #whether or not the user is anonymous if SESSION_KEY in cherrypy.session.keys(): username = cherrypy.session[SESSION_KEY].username anon = False else: username = "******" #at least until we get access control lists working if anon: if branch=="master": #don't let anonymous users modify branch "master" branch = "anonymous" branch = "anonymous" #make the blob blob = Blob.from_string(content) repo = Repo(self.package.path()) #change to the right branch last_head = repo.head() set_head(repo, "master") last_commit = repo.get_object(repo.head()) tree = repo.tree(repo.get_object(repo.head()).tree) #set the file tree[self.filename] = (0100644, blob.id) #make the commit commit = Commit() commit.tree = tree.id commit.parents = [last_head] commit.author = commit.committer = username commit.commit_time = commit.author_time = int(time.time()) commit.commit_timezone = commit.author_timezone = parse_timezone("-0600") commit.encoding = "UTF-8" commit.message = "not implemented yet" repo.object_store.add_object(blob) repo.object_store.add_object(tree) repo.object_store.add_object(commit) repo.refs["refs/heads/" + branch] = commit.id repo.refs["HEAD"] = "ref: refs/heads/" + branch new_link = "<a href=\"/package/" + self.package.name + ":" + branch + "/" + self.filename + "/" + blob.id + "\">go to the new version</a>" self.new_link = new_link self.content = add_newlines("edited (name=%s, branch=%s, sha=%s) new link: %s\n\n\n" % (username, branch, sha, new_link)) self.content = self.content + "<pre>" + content + "</pre>" self.branch = branch return self.respond()
class GitRepo(object): def __init__(self, path): if os.path.exists(path): if not os.path.isdir(path): raise IOError('Git repository "%s" must be a directory.' % path) try: self.repo = Repo(path) except NotGitRepository: # repo does not exist self.repo = Repo.init(path, not os.path.exists(path)) self.temp_persist_files = [] def _get_commit(self, version="HEAD"): commit = self.repo[version] if not isinstance(commit, Commit): raise NotCommitError(commit) return commit def get_type(self, name, version="HEAD"): commit = self._get_commit(version) tree = self.repo.tree(commit.tree) if name not in tree: raise KeyError('Cannot find object "%s"' % name) if tree[name][0] & stat.S_IFDIR: return "tree" else: return "blob" def get_path(self, name, version="HEAD", path_type=None, out_name=None, out_suffix=''): if path_type is None: path_type = self.get_type(name, version) if path_type == 'tree': return self.get_dir(name, version, out_name, out_suffix) elif path_type == 'blob': return self.get_file(name, version, out_name, out_suffix) raise TypeError("Unknown path type '%s'" % path_type) def _write_blob(self, blob_sha, out_fname=None, out_suffix=''): if out_fname is None: # create a temporary file (fd, out_fname) = tempfile.mkstemp(suffix=out_suffix, prefix='vt_persist') os.close(fd) self.temp_persist_files.append(out_fname) else: out_dirname = os.path.dirname(out_fname) if out_dirname and not os.path.exists(out_dirname): os.makedirs(out_dirname) blob = self.repo.get_blob(blob_sha) with open(out_fname, "wb") as f: for b in blob.as_raw_chunks(): f.write(b) return out_fname def get_file(self, name, version="HEAD", out_fname=None, out_suffix=''): commit = self._get_commit(version) tree = self.repo.tree(commit.tree) if name not in tree: raise KeyError('Cannot find blob "%s"' % name) blob_sha = tree[name][1] out_fname = self._write_blob(blob_sha, out_fname, out_suffix) return out_fname def get_dir(self, name, version="HEAD", out_dirname=None, out_suffix=''): if out_dirname is None: # create a temporary directory out_dirname = tempfile.mkdtemp(suffix=out_suffix, prefix='vt_persist') self.temp_persist_files.append(out_dirname) elif not os.path.exists(out_dirname): os.makedirs(out_dirname) commit = self._get_commit(version) tree = self.repo.tree(commit.tree) if name not in tree: raise KeyError('Cannot find tree "%s"' % name) subtree_id = tree[name][1] # subtree = self.repo.tree(subtree_id) for entry in self.repo.object_store.iter_tree_contents(subtree_id): out_fname = os.path.join(out_dirname, entry.path) self._write_blob(entry.sha, out_fname) return out_dirname def get_hash(self, name, version="HEAD", path_type=None): commit = self._get_commit(version) tree = self.repo.tree(commit.tree) if name not in tree: raise KeyError('Cannot find object "%s"' % name) return tree[name][1] @staticmethod def compute_blob_hash(fname, chunk_size=1<<16): obj_len = os.path.getsize(fname) head = object_header(Blob.type_num, obj_len) with open(fname, "rb") as f: def read_chunk(): return f.read(chunk_size) my_iter = chain([head], iter(read_chunk,'')) return iter_sha1(my_iter) return None @staticmethod def compute_tree_hash(dirname): tree = Tree() for entry in sorted(os.listdir(dirname)): fname = os.path.join(dirname, entry) if os.path.isdir(fname): thash = GitRepo.compute_tree_hash(fname) mode = stat.S_IFDIR # os.stat(fname)[stat.ST_MODE] tree.add(entry, mode, thash) elif os.path.isfile(fname): bhash = GitRepo.compute_blob_hash(fname) mode = os.stat(fname)[stat.ST_MODE] tree.add(entry, mode, bhash) return tree.id @staticmethod def compute_hash(path): if os.path.isdir(path): return GitRepo.compute_tree_hash(path) elif os.path.isfile(path): return GitRepo.compute_blob_hash(path) raise TypeError("Do not support this type of path") def get_latest_version(self, path): head = self.repo.head() walker = Walker(self.repo.object_store, [head], max_entries=1, paths=[path]) return iter(walker).next().commit.id def _stage(self, filename): fullpath = os.path.join(self.repo.path, filename) if os.path.islink(fullpath): debug.warning("Warning: not staging symbolic link %s" % os.path.basename(filename)) elif os.path.isdir(fullpath): for f in os.listdir(fullpath): self._stage(os.path.join(filename, f)) else: if os.path.sep != '/': filename = filename.replace(os.path.sep, '/') self.repo.stage(filename) def add_commit(self, filename): self.setup_git() self._stage(filename) commit_id = self.repo.do_commit('Updated %s' % filename) return commit_id def setup_git(self): config_stack = self.repo.get_config_stack() try: config_stack.get(('user',), 'name') config_stack.get(('user',), 'email') except KeyError: from vistrails.core.system import current_user from dulwich.config import ConfigFile user = current_user() repo_conf = self.repo.get_config() repo_conf.set(('user',), 'name', user) repo_conf.set(('user',), 'email', '%s@localhost' % user) repo_conf.write_to_path()
import multiprocessing bind = "0.0.0.0:5000" workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "eventlet" reload = True try: from dulwich.repo import Repo r = Repo('.') # gittle def active_branch(repo, SYMREF=b'ref: ', REFS_BRANCHES=b'refs/heads/'): """Returns the name of the active branch, or None, if HEAD is detached """ x = repo.refs.read_ref('HEAD') if not x.startswith(SYMREF): return b'' else: symref = x[len(SYMREF):] if not symref.startswith(REFS_BRANCHES): return b'' else: return symref[len(REFS_BRANCHES):] import gunicorn gunicorn.SERVER_SOFTWARE = 'MarryBird %s(%s)' % (active_branch(r).decode('ascii'), r.head().decode('ascii')[:11]) except: pass