class MyGit: def __init__(self, gitdir): self.git = Gittle(gitdir) def add(self, files, message): self.git.commit(message=message, files=files) def rm(self, files, message): self.git.rm(files) self.git.commit(message=message)
class Git(object): @classmethod def is_dir_git_repo(cls, directory): return os.system("git rev-parse --is-inside-work-tree") == 0 def __init__(self, gitpath): self._gitpath = gitpath try: self._gittle = Gittle(gitpath) except dulwich.errors.NotGitRepository: raise NoTesseraRepoError() @property def git_dir(self): """ Returns the git dir. """ return self._gittle.git_dir def is_working(self): """ Checks if git is working """ return self._gittle.is_working def commit_repo(self, tesserae, message): """ Commits the git tessera files. """ return self._gittle.commit(message=message, files=[os.path.relpath(tesserae.configpath, self._gitpath)]) def add_tessera(self, tessera): """ Commits a Tessera created by the create() method to the repository. """ return self._gittle.commit(message="tessera created: %s" % tessera.title, files=[os.path.relpath(tessera.tessera_file, self._gitpath), os.path.relpath(tessera.info_file, self._gitpath)]) def update_tessera(self, tessera): """ Commits an updated Tessera to the repository. """ return self._gittle.commit(message="tessera updated: %s" % tessera.title, files=[os.path.relpath(tessera.tessera_file, self._gitpath), os.path.relpath(tessera.info_file, self._gitpath)]) def rm_tessera(self, tessera): """ Removes a tessera and commits to git repository. """ files = [str(os.path.relpath(tessera.tessera_file, self._gitpath)), str(os.path.relpath(tessera.info_file, self._gitpath))] self._gittle.rm(files) return self._gittle.commit(message="tessera removed: %s" % tessera.title, files=files)
class MyGit: def __init__(self, gitdir): self.git = Gittle(gitdir) def add(self, files, message): self.git.commit(message=message, files=files) def rm(self, files, message): self.git.rm(files) self.git.commit(message=message) def read_author(self, tessera_path): author = "unknown" author_time = 0; walker = self.git.repo.get_walker(paths=[tessera_path]) try: c = iter(walker).next().commit except StopIteration: print "git: author not found in %s" % tessera_path else: author = c.author author_time = c.author_time return (author, author_time)
class Wiki(HookMixin): path = None base_path = '/' default_ref = 'master' default_committer_name = 'Anon' default_committer_email = '*****@*****.**' index_page = 'home' gittle = None repo = None def __init__(self, path): try: self.gittle = Gittle(path) except NotGitRepository: self.gittle = Gittle.init(path) # Dulwich repo self.repo = self.gittle.repo self.path = path def __repr__(self): return "Wiki: %s" % self.path def _get_user(self, username, email): if not username: username = self.default_committer_name if not email: email = self.default_committer_email return username, email def _cache_key(self, name, sha='HEAD'): return 'page/%s[%s]' % (name, sha) def revert_page(self, name, commit_sha, message, username, email): """Revert page to passed commit sha1 :param name: Name of page to revert. :param commit_sha: Commit Sha1 to revert to. :param message: Commit message. :param username: Committer name. :param email: Committer email. :return: Git commit sha1 """ page = self.get_page(name, commit_sha) if not page: raise PageNotFound('Commit not found') if not message: commit_info = gittle.utils.git.commit_info( self.gittle[commit_sha.encode('latin-1')]) message = commit_info['message'] return self.write_page(name, page['data'], message=message, username=username, email=email) def write_page(self, name, content, message=None, create=False, username=None, email=None): """Write page to git repo :param name: Name of page. :param content: Content of page. :param message: Commit message. :param create: Perform git add operation? :param username: Commit Name. :param email: Commit Email. :return: Git commit sha1. """ cname = to_canonical(name) filename = cname_to_filename(cname) dirname = posixpath.join(self.path, posixpath.dirname(filename)) if not os.path.exists(dirname): os.makedirs(dirname) with open(self.path + "/" + filename, 'w') as f: f.write(content) if create: self.gittle.add(filename) if not message: message = "Updated %s" % name username, email = self._get_user(username, email) ret = self.gittle.commit(name=username, email=email, message=message, files=[filename]) cache.delete(self._cache_key(cname)) return ret def rename_page(self, old_name, new_name, username=None, email=None, message=None): """Rename page. :param old_name: Page that will be renamed. :param new_name: New name of page. :param username: Committer name :param email: Committer email :return: str -- Commit sha1 """ old_filename, new_filename = map(cname_to_filename, [old_name, new_name]) if old_filename not in self.gittle.index: # old doesn't exist return None if old_filename == new_filename: return if new_filename in self.gittle.index: # file is being overwritten, but that is ok, it's git! pass username, email = self._get_user(username, email) if not message: message = "Moved %s to %s" % (old_name, new_name) os.rename(os.path.join(self.path, old_filename), os.path.join(self.path, new_filename)) self.gittle.add(new_filename) self.gittle.rm(old_filename) commit = self.gittle.commit(name=username, email=email, message=message, files=[old_filename, new_filename]) cache.delete_many(self._cache_key(old_name), self._cache_key(new_name)) return commit def delete_page(self, name, username=None, email=None, message=None): """Delete page. :param name: Page that will be deleted :param username: Committer name :param email: Committer email :return: str -- Commit sha1 """ username, email = self._get_user(username, email) if not message: message = "Deleted %s" % name filename = cname_to_filename(name) # gittle.rm won't actually remove the file, have to do it ourselves os.remove(os.path.join(self.path, filename)) self.gittle.rm(filename) commit = self.gittle.commit(name=username, email=email, message=message, files=[filename]) cache.delete_many(self._cache_key(name)) return commit def get_page(self, name, sha='HEAD'): """Get page data, partials, commit info. :param name: Name of page. :param sha: Commit sha. :return: dict """ cached = cache.get(self._cache_key(name, sha)) if cached: return cached # commit = gittle.utils.git.commit_info(self.repo[sha]) filename = cname_to_filename(name).encode('utf8') sha = sha.encode('latin-1') try: data = self.gittle.get_commit_files(sha, paths=[filename]).get(filename) if not data: return None partials = {} if data.get('data'): meta = self.get_meta(data['data']) if meta and 'import' in meta: for partial_name in meta['import']: partials[partial_name] = self.get_page(partial_name) data['partials'] = partials data['info'] = self.get_history(name, limit=1)[0] cache.set(self._cache_key(name, sha), data) return data except KeyError: # HEAD doesn't exist yet return None def get_meta(self, content): """Get metadata from page if any. :param content: Page content :return: dict """ if not content.startswith("---"): return None meta_end = re.search("\n(\.{3}|\-{3})", content) if not meta_end: return None try: return yaml.safe_load(content[0:meta_end.start()]) except Exception as e: return {'error': e.message} def compare(self, name, old_sha, new_sha): """Compare two revisions of the same page. :param name: Name of page. :param old_sha: Older sha. :param new_sha: Newer sha. :return: str - Raw markup with styles """ # TODO: This could be effectively done in the browser old = self.get_page(name, sha=old_sha) new = self.get_page(name, sha=new_sha) return ghdiff.diff(old['data'], new['data']) def get_index(self): """Get repo index of head. :return: list -- List of dicts """ rv = [] index = self.repo.open_index() for name in index: rv.append( dict(name=filename_to_cname(name), filename=name, ctime=index[name].ctime[0], mtime=index[name].mtime[0], sha=index[name].sha, size=index[name].size)) return rv def get_history(self, name, limit=100): """Get page history. :param name: Name of page. :param limit: Limit history size. :return: list -- List of dicts """ if not len(self.repo.open_index()): # Index is empty, no commits return [] file_path = cname_to_filename(name) versions = [] walker = self.repo.get_walker(paths=[file_path], max_entries=limit) for entry in walker: change_type = None for change in entry.changes(): if change.old.path == file_path: change_type = change.type elif change.new.path == file_path: change_type = change.type author_name, author_email = entry.commit.author.rstrip('>').split( '<') versions.append( dict(author=author_name.strip(), author_email=author_email, time=entry.commit.author_time, message=entry.commit.message, sha=entry.commit.id, type=change_type)) return versions
class Wiki(HookMixin): path = None base_path = '/' default_ref = 'master' default_committer_name = 'Anon' default_committer_email = '*****@*****.**' index_page = 'home' gittle = None repo = None def __init__(self, path): try: self.gittle = Gittle(path) except NotGitRepository: self.gittle = Gittle.init(path) # Dulwich repo self.repo = self.gittle.repo self.path = path def __repr__(self): return "Wiki: %s" % self.path def revert_page(self, name, commit_sha, message, username): page = self.get_page(name, commit_sha) if not page: # Page not found return None commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')]) message = commit_info['message'] return self.write_page(name, page['data'], message=message, username=username) def write_page(self, name, content, message=None, create=False, username=None, email=None): def escape_repl(m): if m.group(1): return "```" + escape(m.group(1)) + "```" def unescape_repl(m): if m.group(1): return "```" + unescape(m.group(1)) + "```" cname = to_canonical(name) # prevents p tag from being added, we remove this later content = '<div>' + content + '</div>' content = re.sub(r"```(.*?)```", escape_repl, content, flags=re.DOTALL) tree = lxml.html.fromstring(content) cleaner = Cleaner(remove_unknown_tags=False, kill_tags=set(['style']), safe_attrs_only=False) tree = cleaner.clean_html(tree) content = lxml.html.tostring(tree, encoding='utf-8', method='html') # remove added div tags content = content[5:-6] # FIXME this is for block quotes, doesn't work for double ">" content = re.sub(r"(\n>)", "\n>", content) content = re.sub(r"(^>)", ">", content) # Handlebars partial ">" content = re.sub(r"\{\{>(.*?)\}\}", r'{{>\1}}', content) # Handlebars, allow {{}} inside HTML links content = content.replace("%7B", "{") content = content.replace("%7D", "}") content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL) filename = cname_to_filename(cname) with open(self.path + "/" + filename, 'w') as f: f.write(content) if create: self.gittle.add(filename) if not message: message = "Updated %s" % name if not username: username = self.default_committer_name if not email: email = self.default_committer_email ret = self.gittle.commit(name=username, email=email, message=message, files=[filename]) cache.delete(cname) return ret def rename_page(self, old_name, new_name, user=None): old_filename, new_filename = map(cname_to_filename, [old_name, new_name]) if old_filename not in self.gittle.index: # old doesn't exist return None if new_filename in self.gittle.index: # file is being overwritten, but that is ok, it's git! pass os.rename(os.path.join(self.path, old_filename), os.path.join(self.path, new_filename)) self.gittle.add(new_filename) self.gittle.rm(old_filename) self.gittle.commit(name=getattr(user, 'username', self.default_committer_name), email=getattr(user, 'email', self.default_committer_email), message="Moved %s to %s" % (old_name, new_name), files=[old_filename, new_filename]) cache.delete_many(old_filename, new_filename) def get_page(self, name, sha='HEAD'): cached = cache.get(name) if cached: return cached # commit = gittle.utils.git.commit_info(self.repo[sha]) filename = cname_to_filename(name).encode('latin-1') sha = sha.encode('latin-1') try: data = self.gittle.get_commit_files(sha, paths=[filename]).get(filename) if not data: return None partials = {} if data.get('data'): meta = self.get_meta(data['data']) if meta and 'import' in meta: for partial_name in meta['import']: partials[partial_name] = self.get_page(partial_name) data['partials'] = partials data['info'] = self.get_history(name, limit=1)[0] return data except KeyError: # HEAD doesn't exist yet return None def get_meta(self, content): if not content.startswith("---"): return None meta_end = re.search("\n(\.{3}|\-{3})", content) if not meta_end: return None try: return yaml.safe_load(content[0:meta_end.start()]) except Exception as e: return {'error': e.message} def compare(self, name, old_sha, new_sha): old = self.get_page(name, sha=old_sha) new = self.get_page(name, sha=new_sha) return ghdiff.diff(old['data'], new['data']) def get_index(self): rv = [] index = self.repo.open_index() for name in index: rv.append(dict(name=filename_to_cname(name), filename=name, ctime=index[name].ctime[0], mtime=index[name].mtime[0], sha=index[name].sha, size=index[name].size)) return rv def get_history(self, name, limit=100): file_path = cname_to_filename(name) versions = [] walker = self.repo.get_walker(paths=[file_path], max_entries=limit) for entry in walker: change_type = None for change in entry.changes(): if change.old.path == file_path: change_type = change.type elif change.new.path == file_path: change_type = change.type author_name, author_email = entry.commit.author.split('<') versions.append(dict( author=author_name.strip(), time=entry.commit.author_time, message=entry.commit.message, sha=entry.commit.id, type=change_type)) return versions
def git_rm(args): if len(args) > 0: repo = Gittle('.') repo.rm(args) else: print command_help['rm']
class Wiki(HookMixin): path = None base_path = '/' default_ref = 'master' default_committer_name = 'Anon' default_committer_email = '*****@*****.**' index_page = 'home' gittle = None repo = None def __init__(self, path): try: self.gittle = Gittle(path) except NotGitRepository: self.gittle = Gittle.init(path) # Dulwich repo self.repo = self.gittle.repo self.path = path def __repr__(self): return "Wiki: %s" % self.path def _get_user(self, username, email): if not username: username = self.default_committer_name if not email: email = self.default_committer_email return username, email def revert_page(self, name, commit_sha, message, username, email): """Revert page to passed commit sha1 :param name: Name of page to revert. :param commit_sha: Commit Sha1 to revert to. :param message: Commit message. :param username: Committer name. :param email: Committer email. :return: Git commit sha1 """ page = self.get_page(name, commit_sha) if not page: raise PageNotFound('Commit not found') if not message: commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')]) message = commit_info['message'] return self.write_page(name, page['data'], message=message, username=username, email=email) def write_page(self, name, content, message=None, create=False, username=None, email=None): """Write page to git repo :param name: Name of page. :param content: Content of page. :param message: Commit message. :param create: Perform git add operation? :param username: Commit Name. :param email: Commit Email. :return: Git commit sha1. """ cname = to_canonical(name) filename = cname_to_filename(cname) namespace_path = os.path.join(self.path, os.path.split(filename)[0]) if not os.path.exists(namespace_path): os.makedirs(namespace_path) with open(self.path + "/" + filename, 'w') as f: f.write(content) if create: self.gittle.add(filename) if not message: message = "Updated %s" % name username, email = self._get_user(username, email) ret = self.gittle.commit(name=username, email=email, message=message, files=[filename]) cache.delete(cname) return ret def rename_page(self, old_name, new_name, username=None, email=None, message=None): """Rename page. :param old_name: Page that will be renamed. :param new_name: New name of page. :param username: Committer name :param email: Committer email :return: str -- Commit sha1 """ old_filename, new_filename = map(cname_to_filename, [old_name, new_name]) if old_filename not in self.gittle.index: # old doesn't exist return None if new_filename in self.gittle.index: # file is being overwritten, but that is ok, it's git! pass username, email = self._get_user(username, email) if not message: message = "Moved %s to %s" % (old_name, new_name) os.rename(os.path.join(self.path, old_filename), os.path.join(self.path, new_filename)) self.gittle.add(new_filename) self.gittle.rm(old_filename) commit = self.gittle.commit(name=username, email=email, message=message, files=[old_filename, new_filename]) cache.delete_many(old_name, new_name) return commit def delete_page(self, name, username=None, email=None, message=None): """Delete page. :param name: Page that will be deleted :param username: Committer name :param email: Committer email :return: str -- Commit sha1 """ username, email = self._get_user(username, email) if not message: message = "Deleted %s" % name filename = cname_to_filename(name) self.gittle.rm(filename) commit = self.gittle.commit(name=username, email=email, message=message, files=[str(filename)]) cache.delete_many(name) return commit def get_page(self, name, sha='HEAD'): """Get page data, partials, commit info. :param name: Name of page. :param sha: Commit sha. :return: dict """ cached = cache.get(name) if cached: return cached # commit = gittle.utils.git.commit_info(self.repo[sha]) filename = cname_to_filename(name).encode('latin-1') sha = sha.encode('latin-1') namespace_path = os.path.join(self.path, os.path.splitext(filename)[0]) namespace_cname = to_canonical(os.path.splitext(filename)[0]) if not os.path.exists(os.path.join(self.path, filename)) and os.path.isdir(namespace_path): files = ["[%s](%s_%s)" % (x, namespace_cname, filename_to_cname(x)) for x in os.listdir(namespace_path)] print(files) return {'data': "# Namespace %s \n\n This is an automatically generated list of pages in this namespace.\n\n %s" % (os.path.splitext(filename)[0], '\n'.join(files))} try: data = self.gittle.get_commit_files(sha, paths=[filename]).get(filename) if not data: return None partials = {} if data.get('data'): meta = self.get_meta(data['data']) if meta and 'import' in meta: for partial_name in meta['import']: partials[partial_name] = self.get_page(partial_name) data['partials'] = partials data['info'] = self.get_history(name, limit=1)[0] return data except KeyError: # HEAD doesn't exist yet return None def get_meta(self, content): """Get metadata from page if any. :param content: Page content :return: dict """ if not content.startswith("---"): return None meta_end = re.search("\n(\.{3}|\-{3})", content) if not meta_end: return None try: return yaml.safe_load(content[0:meta_end.start()]) except Exception as e: return {'error': e.message} def compare(self, name, old_sha, new_sha): """Compare two revisions of the same page. :param name: Name of page. :param old_sha: Older sha. :param new_sha: Newer sha. :return: str - Raw markup with styles """ # TODO: This could be effectively done in the browser old = self.get_page(name, sha=old_sha) new = self.get_page(name, sha=new_sha) return ghdiff.diff(old['data'], new['data']) def get_index(self): """Get repo index of head. :return: list -- List of dicts """ rv = [] index = self.repo.open_index() for name in index: rv.append(dict(name=filename_to_cname(name), filename=name, ctime=index[name].ctime[0], mtime=index[name].mtime[0], sha=index[name].sha, size=index[name].size)) return rv def get_history(self, name, limit=100): """Get page history. :param name: Name of page. :param limit: Limit history size. :return: list -- List of dicts """ if not len(self.repo.open_index()): # Index is empty, no commits return [] file_path = cname_to_filename(name) versions = [] walker = self.repo.get_walker(paths=[file_path], max_entries=limit) for entry in walker: change_type = None for change in entry.changes(): if change.old.path == file_path: change_type = change.type elif change.new.path == file_path: change_type = change.type author_name, author_email = entry.commit.author.split('<') versions.append(dict( author=author_name.strip(), time=entry.commit.author_time, message=entry.commit.message, sha=entry.commit.id, type=change_type)) return versions
class GitCommands(object): def __init__(self): self.gitdir = "." self.git = Gittle(self.gitdir) Tessera._tesserae = os.path.relpath("%s/.tesserae" % self.gitdir) def cmd_init(self, args): if len(args) != 0: stderr.write("git tessera init takes no arguments\n") return False #if self.git.is_dirty(): #stderr.write("repo is dirty\n") #return False if os.path.exists(Tessera._tesserae): stderr.write("git tesserae directory already exists: %s\n" % Tessera._tesserae) return False os.mkdir(Tessera._tesserae) files = [] t = "%s/template" % Tessera._tesserae shutil.copyfile("%s/config/template" % os.path.dirname(os.path.realpath(__file__)), t) files.append(t) t = "%s/status" % Tessera._tesserae shutil.copyfile("%s/config/status" % os.path.dirname(os.path.realpath(__file__)), t) files.append(t) t = "%s/types" % Tessera._tesserae shutil.copyfile("%s/config/types" % os.path.dirname(os.path.realpath(__file__)), t) files.append(t) self.git_add(files, "tessera: initialized") return True def cmd_ls(self, args): gt = GitTessera() tesserae = gt.ls(args) for t in tesserae: print t.summary() return True def cmd_show(self, args): if len(args) != 1: stderr.write("git tessera show takes identifier as argument\n") return False gt = GitTessera() t = gt.get(args[0]) if not t: return False short = t.summary() length = len(short) print "=" * length print short print "=" * length print t.content return True def cmd_edit(self, args): if len(args) < 1: stderr.write("git tessera edit takes one or more identifier as argument\n") return False tessera_paths = [] for key in args: tessera_path = None found = False for i in os.listdir(Tessera._tesserae): tessera_path = "%s/%s" % (Tessera._tesserae, i) if not stat.S_ISDIR(os.lstat(tessera_path).st_mode): continue if i.split('-')[0] == key or i == key: found = True break if not found: stderr.write("git tessera %s not found\n" % key) return False tessera_paths.append(tessera_path) tessera_files = ["%s/tessera" % x for x in tessera_paths] p = Popen(["sensible-editor"] + tessera_files) p.communicate() p.wait() #if self.git.is_dirty(): for tessera_path in tessera_paths: t = Tessera(tessera_path) self.git_add("%s/tessera" % tessera_path, "tessera updated: %s" % t.title) return True def cmd_create(self, args): if len(args) < 1: stderr.write("git tessera create needs arguments\n") return False #if self.git.is_dirty(): # stderr.write("repo is dirty\n") # return False if args: title = " ".join(args) else: title = "tessera title goes here" uuid = uuid1() tessera_path = "%s/%s" % (Tessera._tesserae, uuid) tessera_file = "%s/tessera" % tessera_path os.mkdir(tessera_path) fin = open("%s/template" % Tessera._tesserae, "r") fout = open(tessera_file, "w") for line in fin.readlines(): if line == "@title@\n": line = "# %s\n" % title fout.write(line) fin.close() fout.close() p = Popen(["sensible-editor", tessera_file]) p.communicate() p.wait() t = Tessera(tessera_path) self.git_add(tessera_file, "tessera created: %s" % t.get_attribute("title")) return True def cmd_remove(self, args): if len(args) != 1: stderr.write("git tessera remove takes identifier as argument\n") return False #if self.git.is_dirty(): #stderr.write("repo is dirty\n") #return False key = args[0] tessera_file = None tessera_path = None for i in os.listdir(Tessera._tesserae): tessera_path = "%s/%s" % (Tessera._tesserae, i) if not stat.S_ISDIR(os.lstat(tessera_path).st_mode): continue if i.split('-')[0] == key or i == key: tessera_file = "%s/tessera" % tessera_path break if not tessera_file: stderr.write("git tessera %s not found\n" % key) return False t = Tessera(tessera_path) stdout.write("remove tessera %s: %s ? [Y/n] " % (key, t.get_attribute("title"))) try: answer = stdin.readline().strip() except KeyboardInterrupt: return False if not answer or answer.lower() == "y": files = ["%s/%s" % (tessera_path, x) for x in os.listdir(tessera_path)] self.git_rm(files, "tessera removed: %s" % t.get_attribute("title")) from shutil import rmtree rmtree(tessera_path) def cmd_serve(self, args): from tesseraweb import TesseraWeb web = TesseraWeb() web.serve() def cmd_tag(self, args): if len(args) != 2: stderr.write("git tessera show takes identifier as argument and new tag\n") return False key = args[0] for i in os.listdir(Tessera._tesserae): tessera_path = "%s/%s" % (Tessera._tesserae, i) if not stat.S_ISDIR(os.lstat(tessera_path).st_mode): continue if i.split('-')[0] == key or i == key: break if not tessera_path: stderr.write("git tessera %s not found\n" % key) return False t = Tessera(tessera_path) t.add_tag(args[1]) self.git_add(t.filename, "tessera updated: add tag %s to %s" % (args[1], t.get_attribute("title"))) return True def git_add(self, files, message): stderr.write("staging %s" % files) self.git.commit(message=message, files=files) def git_rm(self, files, message): self.git.rm(files) self.git.commit(message=message)