From abe825dee6ea0883a2ed04277004534abb7e6c4e Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Tue, 9 Oct 2018 19:58:03 +0100 Subject: [PATCH 01/12] initial --- .gitignore | 179 ++++++++++++++++++++++++++++++++++++++++++++ commits/__main__.py | 0 2 files changed, 179 insertions(+) create mode 100644 .gitignore create mode 100644 commits/__main__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cbc4ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,179 @@ + +# Created by https://www.gitignore.io/api/python,emacs + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +### Python Patch ### +.venv/ + +### Python.VirtualEnv Stack ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +pip-selfcheck.json + + +# End of https://www.gitignore.io/api/python,emacs diff --git a/commits/__main__.py b/commits/__main__.py new file mode 100644 index 0000000..e69de29 From f3278ec1db0eaae7dc6342c589555177ffeb5767 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Tue, 9 Oct 2018 22:38:58 +0100 Subject: [PATCH 02/12] Basic implementation for commit provider --- commits/__init__.py | 73 +++++++++++++++++++++++++++++++++++++++++++++ commits/__main__.py | 5 ++++ requirements.txt | 1 + 3 files changed, 79 insertions(+) create mode 100644 commits/__init__.py create mode 100644 requirements.txt diff --git a/commits/__init__.py b/commits/__init__.py new file mode 100644 index 0000000..83d8db9 --- /dev/null +++ b/commits/__init__.py @@ -0,0 +1,73 @@ +from datetime import datetime +from typing import List, NamedTuple, Optional +from os.path import basename, islink, isdir, join +from os import listdir + +import git # type: ignore + +# TODO do something smarter... later +# TODO def run against bitbucket and gh backups +SOURCES = [ + '***REMOVED***', + '***REMOVED***', + '***REMOVED***', + '***REMOVED***', + '***REMOVED***', + '***REMOVED***', +] + +THINGS = [ + '***REMOVED***', + '***REMOVED***', + '***REMOVED***', + '***REMOVED***', +] + +def by_me(actor): + aa = actor.email + " " + actor.name + if actor.email in ('***REMOVED***', '***REMOVED***@gmail.com'): + return True + if actor.name in ('***REMOVED***',): + return True + for thing in THINGS: + if thing in aa: + print("WARNING!!!", actor) + return True + return False + +class Commit(NamedTuple): + dt: datetime + message: str + repo: str + # TODO filter so they are authored by me + +def iter_commits(repo: str): + # TODO other branches? + rr = basename(repo) + gr = git.Repo(repo) + for c in gr.head.reference.log(): + if by_me(c.actor): + yield Commit( + dt=c.time, + message=c.message, # TODO strip off 'commit: '? (there are also 'merge') + repo=rr, + ) + +def is_git_repo(d: str): + dotgit = join(d, '.git') + return isdir(dotgit) + +# TODO eh. traverse all of filesystem?? or only specific dirs for now? +def iter_all_commits(): + for src in SOURCES: + # TODO warn if doesn't exist? + for d in listdir(src): + pr = join(src, d) + if is_git_repo(pr): + for c in iter_commits(pr): + yield c + + +def get_all_commits(): + ss = set(iter_all_commits()) + return list(sorted(ss, key=lambda c: c.dt)) diff --git a/commits/__main__.py b/commits/__main__.py index e69de29..ee2eee1 100644 --- a/commits/__main__.py +++ b/commits/__main__.py @@ -0,0 +1,5 @@ +from commits import iter_commits, iter_all_commits, get_all_commits + +# TODO cache? +for c in get_all_commits(): # ('***REMOVED***'): + print(c) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..59348f9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +gitpython From cc72221ee226e7e339521653746a511c176aed37 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Tue, 9 Oct 2018 22:55:31 +0100 Subject: [PATCH 03/12] Extract actual commits; handle errors --- commits/__init__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/commits/__init__.py b/commits/__init__.py index 83d8db9..9096dd5 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -45,11 +45,11 @@ def iter_commits(repo: str): # TODO other branches? rr = basename(repo) gr = git.Repo(repo) - for c in gr.head.reference.log(): - if by_me(c.actor): + for c in gr.iter_commits(): + if by_me(c.author): yield Commit( - dt=c.time, - message=c.message, # TODO strip off 'commit: '? (there are also 'merge') + dt=c.committed_datetime, # TODO authored?? + message=c.message.strip(), repo=rr, ) @@ -64,8 +64,14 @@ def iter_all_commits(): for d in listdir(src): pr = join(src, d) if is_git_repo(pr): - for c in iter_commits(pr): - yield c + try: + for c in iter_commits(pr): + yield c + except ValueError as ve: + if "Reference at 'refs/heads/master' does not exist" in str(ve): + continue # TODO wtf??? log? + else: + raise ve def get_all_commits(): From 6b0cd6a9a7afbf92dcdfcdabf7cb798567046049 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Tue, 16 Oct 2018 22:36:07 +0100 Subject: [PATCH 04/12] use sha, use only one commit per sha --- commits/__init__.py | 13 +++++++++++-- run | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100755 run diff --git a/commits/__init__.py b/commits/__init__.py index 9096dd5..a7944dd 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -39,6 +39,7 @@ class Commit(NamedTuple): dt: datetime message: str repo: str + sha: str # TODO filter so they are authored by me def iter_commits(repo: str): @@ -51,6 +52,7 @@ def iter_commits(repo: str): dt=c.committed_datetime, # TODO authored?? message=c.message.strip(), repo=rr, + sha=c.hexsha, ) def is_git_repo(d: str): @@ -75,5 +77,12 @@ def iter_all_commits(): def get_all_commits(): - ss = set(iter_all_commits()) - return list(sorted(ss, key=lambda c: c.dt)) + res = {} + for c in iter_all_commits(): + nn = res.get(c.sha, None) + if nn is None: + res[c.sha] = c + else: + res[c.sha] = min(nn, c, key=lambda c: c.sha) + + return list(sorted(res.values(), key=lambda c: c.dt)) diff --git a/run b/run new file mode 100755 index 0000000..e60aa44 --- /dev/null +++ b/run @@ -0,0 +1,3 @@ +#!/bin/bash +set -eu +python3 -m commits From c7b266579a6e8bc28c44a7b6c16de18d7278e7ba Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sat, 10 Nov 2018 14:34:21 +0000 Subject: [PATCH 05/12] Patch timezone so it uses standard python object --- commits/__init__.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/commits/__init__.py b/commits/__init__.py index a7944dd..4a582c0 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from typing import List, NamedTuple, Optional from os.path import basename, islink, isdir, join from os import listdir @@ -42,6 +42,16 @@ class Commit(NamedTuple): sha: str # TODO filter so they are authored by me +# TODO not sure, maybe a better idea to move it to timeline? +def fix_datetime(dt) -> datetime: + # git module got it's own tzinfo object.. and it's pretty weird + tz = dt.tzinfo + assert tz._name == 'fixed' + offset = tz._offset + ntz = timezone(offset) + return dt.replace(tzinfo=ntz) + + def iter_commits(repo: str): # TODO other branches? rr = basename(repo) @@ -49,7 +59,7 @@ def iter_commits(repo: str): for c in gr.iter_commits(): if by_me(c.author): yield Commit( - dt=c.committed_datetime, # TODO authored?? + dt=fix_datetime(c.committed_datetime), # TODO authored?? message=c.message.strip(), repo=rr, sha=c.hexsha, From eecfaf43cd1754ea426fffdaf8981f389840662b Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Fri, 14 Dec 2018 07:35:14 +0000 Subject: [PATCH 06/12] Fix checking for author --- commits/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commits/__init__.py b/commits/__init__.py index 4a582c0..05ed196 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -24,11 +24,11 @@ THINGS = [ ] def by_me(actor): - aa = actor.email + " " + actor.name if actor.email in ('***REMOVED***', '***REMOVED***@gmail.com'): return True if actor.name in ('***REMOVED***',): return True + aa = f"{actor.email} {actor.name}" for thing in THINGS: if thing in aa: print("WARNING!!!", actor) From c0870f9adf6ea57821771a9bbe9a901cf9a0fe4b Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Tue, 12 Mar 2019 12:00:14 +0000 Subject: [PATCH 07/12] make ruci happy --- commits/__init__.py | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/commits/__init__.py b/commits/__init__.py index 05ed196..0a48f01 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -1,5 +1,5 @@ from datetime import datetime, timezone -from typing import List, NamedTuple, Optional +from typing import List, NamedTuple, Optional, Dict, Any from os.path import basename, islink, isdir, join from os import listdir @@ -23,7 +23,8 @@ THINGS = [ '***REMOVED***', ] -def by_me(actor): +def by_me(c): + actor = c.author if actor.email in ('***REMOVED***', '***REMOVED***@gmail.com'): return True if actor.name in ('***REMOVED***',): @@ -31,7 +32,7 @@ def by_me(actor): aa = f"{actor.email} {actor.name}" for thing in THINGS: if thing in aa: - print("WARNING!!!", actor) + print("WARNING!!!", actor, c, c.repo) return True return False @@ -40,6 +41,7 @@ class Commit(NamedTuple): message: str repo: str sha: str + ref: Optional[str]=None # TODO filter so they are authored by me # TODO not sure, maybe a better idea to move it to timeline? @@ -52,26 +54,41 @@ def fix_datetime(dt) -> datetime: return dt.replace(tzinfo=ntz) -def iter_commits(repo: str): +def iter_commits(repo: str, ref=None): # TODO other branches? rr = basename(repo) gr = git.Repo(repo) - for c in gr.iter_commits(): - if by_me(c.author): + for c in gr.iter_commits(rev=ref): + if by_me(c): yield Commit( dt=fix_datetime(c.committed_datetime), # TODO authored?? message=c.message.strip(), repo=rr, sha=c.hexsha, + ref=ref, ) +def iter_all_ref_commits(repo): + rr = basename(repo) + gr = git.Repo(repo) + for r in gr.references: + yield from iter_commits(repo=repo, ref=r) + + def is_git_repo(d: str): dotgit = join(d, '.git') return isdir(dotgit) -# TODO eh. traverse all of filesystem?? or only specific dirs for now? -def iter_all_commits(): - for src in SOURCES: +from pathlib import Path +from typing import Union +PathIsh = Union[str, Path] + +def iter_all_git_repos(dd: PathIsh): + dd = Path(dd) + yield from dd.glob('**/.git') + +def iter_multi_commits(sources): + for src in sources: # TODO warn if doesn't exist? for d in listdir(src): pr = join(src, d) @@ -85,9 +102,13 @@ def iter_all_commits(): else: raise ve +# TODO eh. traverse all of filesystem?? or only specific dirs for now? +def iter_all_commits(): + return iter_multi_commits(SOURCES) + def get_all_commits(): - res = {} + res: Dict[str, Any] = {} for c in iter_all_commits(): nn = res.get(c.sha, None) if nn is None: From 47ec112a409685a0d42be3f7395edb829832e94a Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Wed, 20 Mar 2019 23:29:54 +0000 Subject: [PATCH 08/12] better reference detection --- commits/__init__.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/commits/__init__.py b/commits/__init__.py index 0a48f01..098391e 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -1,8 +1,12 @@ from datetime import datetime, timezone -from typing import List, NamedTuple, Optional, Dict, Any +from typing import List, NamedTuple, Optional, Dict, Any, Iterator +from pathlib import Path from os.path import basename, islink, isdir, join from os import listdir +from kython.ktyping import PathIsh + +# pip3 install gitpython import git # type: ignore # TODO do something smarter... later @@ -53,12 +57,15 @@ def fix_datetime(dt) -> datetime: ntz = timezone(offset) return dt.replace(tzinfo=ntz) +from kython.ktyping import PathIsh -def iter_commits(repo: str, ref=None): +def iter_commits(repo: PathIsh, ref=None): # TODO other branches? - rr = basename(repo) + repo = Path(repo) + rr = repo.stem gr = git.Repo(repo) - for c in gr.iter_commits(rev=ref): + # without path might not handle pull heads properly + for c in gr.iter_commits(rev=ref.path): if by_me(c): yield Commit( dt=fix_datetime(c.committed_datetime), # TODO authored?? @@ -68,9 +75,9 @@ def iter_commits(repo: str, ref=None): ref=ref, ) -def iter_all_ref_commits(repo): - rr = basename(repo) - gr = git.Repo(repo) +def iter_all_ref_commits(repo: Path): + # TODO hmm, git library has got way of determining git.. + gr = git.Repo(str(repo)) for r in gr.references: yield from iter_commits(repo=repo, ref=r) @@ -79,14 +86,15 @@ def is_git_repo(d: str): dotgit = join(d, '.git') return isdir(dotgit) -from pathlib import Path -from typing import Union -PathIsh = Union[str, Path] -def iter_all_git_repos(dd: PathIsh): +def iter_all_git_repos(dd: PathIsh) -> Iterator[Path]: + # TODO would that cover all repos??? dd = Path(dd) - yield from dd.glob('**/.git') + for xx in dd.glob('**/refs/heads/'): + yield xx.parent.parent + +# TODO is it only used in wcommits? def iter_multi_commits(sources): for src in sources: # TODO warn if doesn't exist? From be7c0a1dc83c1c7b11a5488c7508603da4ae1ef7 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Fri, 5 Apr 2019 23:07:30 +0100 Subject: [PATCH 09/12] some old WIP on distinguishing timestamps + add test --- commits/__init__.py | 32 ++++++++++++++++++++++++++------ commits/__main__.py | 5 ----- commits/test.py | 7 +++++++ run | 3 --- 4 files changed, 33 insertions(+), 14 deletions(-) delete mode 100644 commits/__main__.py create mode 100644 commits/test.py delete mode 100755 run diff --git a/commits/__init__.py b/commits/__init__.py index 098391e..4c8a2b9 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -41,13 +41,18 @@ def by_me(c): return False class Commit(NamedTuple): - dt: datetime + commited_dt: datetime + authored_dt: datetime message: str repo: str sha: str ref: Optional[str]=None # TODO filter so they are authored by me + @property + def dt(self) -> datetime: + return self.commited_dt + # TODO not sure, maybe a better idea to move it to timeline? def fix_datetime(dt) -> datetime: # git module got it's own tzinfo object.. and it's pretty weird @@ -62,13 +67,14 @@ from kython.ktyping import PathIsh def iter_commits(repo: PathIsh, ref=None): # TODO other branches? repo = Path(repo) - rr = repo.stem + rr = repo.name gr = git.Repo(repo) # without path might not handle pull heads properly for c in gr.iter_commits(rev=ref.path): if by_me(c): yield Commit( - dt=fix_datetime(c.committed_datetime), # TODO authored?? + commited_dt=fix_datetime(c.committed_datetime), + authored_dt=fix_datetime(c.authored_datetime), message=c.message.strip(), repo=rr, sha=c.hexsha, @@ -76,7 +82,6 @@ def iter_commits(repo: PathIsh, ref=None): ) def iter_all_ref_commits(repo: Path): - # TODO hmm, git library has got way of determining git.. gr = git.Repo(str(repo)) for r in gr.references: yield from iter_commits(repo=repo, ref=r) @@ -86,12 +91,18 @@ def is_git_repo(d: str): dotgit = join(d, '.git') return isdir(dotgit) +from git.repo.fun import is_git_dir # type: ignore def iter_all_git_repos(dd: PathIsh) -> Iterator[Path]: # TODO would that cover all repos??? dd = Path(dd) - for xx in dd.glob('**/refs/heads/'): - yield xx.parent.parent + for xx in dd.glob('**/HEAD'): # ugh + c = xx.parent + if not is_git_dir(c): + continue + if c.name == '.git': + c = c.parent + yield c # TODO is it only used in wcommits? @@ -125,3 +136,12 @@ def get_all_commits(): res[c.sha] = min(nn, c, key=lambda c: c.sha) return list(sorted(res.values(), key=lambda c: c.dt)) + + +def main(): + for c in get_all_commits(): # ('***REMOVED***'): + print(c) + + +if __name__ == '__main__': + main() diff --git a/commits/__main__.py b/commits/__main__.py deleted file mode 100644 index ee2eee1..0000000 --- a/commits/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -from commits import iter_commits, iter_all_commits, get_all_commits - -# TODO cache? -for c in get_all_commits(): # ('***REMOVED***'): - print(c) diff --git a/commits/test.py b/commits/test.py new file mode 100644 index 0000000..8d39aa0 --- /dev/null +++ b/commits/test.py @@ -0,0 +1,7 @@ +from . import get_all_commits + +# TODO shit. why can't it just be in __init__.py?? + +def test(): + commits = get_all_commits() + assert len(commits) > 10 diff --git a/run b/run deleted file mode 100755 index e60aa44..0000000 --- a/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -set -eu -python3 -m commits From c9312c2950830c53237dc42f5ce539427c3ab670 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Fri, 5 Apr 2019 23:23:10 +0100 Subject: [PATCH 10/12] fix bug --- commits/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commits/__init__.py b/commits/__init__.py index 4c8a2b9..5a7be36 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -70,7 +70,7 @@ def iter_commits(repo: PathIsh, ref=None): rr = repo.name gr = git.Repo(repo) # without path might not handle pull heads properly - for c in gr.iter_commits(rev=ref.path): + for c in gr.iter_commits(rev=None if ref is None else ref.path): if by_me(c): yield Commit( commited_dt=fix_datetime(c.committed_datetime), From 52660f3744be4ffa8e7b0a443b1f5cd0c3ade92e Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Fri, 26 Apr 2019 23:09:51 +0100 Subject: [PATCH 11/12] fix paths --- commits/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commits/__init__.py b/commits/__init__.py index 5a7be36..4fbe432 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -11,13 +11,13 @@ import git # type: ignore # TODO do something smarter... later # TODO def run against bitbucket and gh backups +# TODO github/bitbucket repos? SOURCES = [ '***REMOVED***', '***REMOVED***', '***REMOVED***', '***REMOVED***', '***REMOVED***', - '***REMOVED***', ] THINGS = [ From 167ca8a8f3b364e5b57f17b9bca3b7e7a5eb06a5 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Tue, 14 May 2019 13:52:04 +0200 Subject: [PATCH 12/12] compute canonical name --- commits/__init__.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/commits/__init__.py b/commits/__init__.py index 4fbe432..8f37cc4 100644 --- a/commits/__init__.py +++ b/commits/__init__.py @@ -12,11 +12,11 @@ import git # type: ignore # TODO do something smarter... later # TODO def run against bitbucket and gh backups # TODO github/bitbucket repos? +# TODO FIXME syncthing? or not necessary with coding view?? SOURCES = [ '***REMOVED***', - '***REMOVED***', - '***REMOVED***', - '***REMOVED***', + # '***REMOVED***', + # '***REMOVED***', '***REMOVED***', ] @@ -96,6 +96,7 @@ from git.repo.fun import is_git_dir # type: ignore def iter_all_git_repos(dd: PathIsh) -> Iterator[Path]: # TODO would that cover all repos??? dd = Path(dd) + assert dd.exists() for xx in dd.glob('**/HEAD'): # ugh c = xx.parent if not is_git_dir(c): @@ -105,6 +106,21 @@ def iter_all_git_repos(dd: PathIsh) -> Iterator[Path]: yield c +def canonical_name(repo: Path) -> str: + if repo.match('github/repositories/*/repository'): + return repo.parent.name + else: + return repo.name + + # if r.name == 'repository': # 'repository' thing from github.. + # rname = r.parent.name + # else: + # rname = r.name + # if 'backups/github' in repo: + # pass # TODO + pass + + # TODO is it only used in wcommits? def iter_multi_commits(sources): for src in sources: