Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- """
- Tool to do git commands in parallel across five repos
- that ignore submodule tracking and instead rely on
- having branches with the same name.
- Save this somewhere on your PATH as metagit and
- chmod +x it. Make sure you have Python 3.6+. And:
- $ pip3 install click python-dateutil gitpython
- Then you can do this:
- $ metagit asof 20190906T12 --checkout
- ... which will find and checkout the last commit on
- your current branch before (local) 2019-09-06T12:00:00
- in each repo. Unless you're really unlucky (e.g.,
- someone committed to XLE at 11:59 and then made the
- corresponding commit to cocos3d at 12:03), this should
- give you a buildable state.
- Leave off the --checkout to just list the commits
- without automatically checking them out; leave off the
- timestamp to go to HEAD everywhere.
- This uses the normal git checkout logic, so if one of
- your repos isn't in a clean state, it won't get blown
- away, you'll get the usual error telling you to stash
- or commit your changes first, but it will be uglier.
- And you'll have 0-4 repos that checked out beforehand
- successfully and 0-4 that didn't get done, so you'll
- probably want to go back to HEAD on all of them.
- If your current branch isn't drg-metal on any repo,
- you probably won't be happy.
- It would be useful to have commands to pull all branches,
- to stash/pull/apply, to stash/asof/apply, and to log
- (with the entries from all five repos interleaved
- as appropriate), but I haven't written them.
- """
- import datetime
- import os
- import click
- import dateutil
- import dateutil.parser
- import git
- projects = (
- 'dragons3d',
- 'dragons3d/Externals/cocos3d',
- 'dragons3d/Externals/cocos3d/XLE',
- 'dragons3d/Externals/pgengine',
- 'dragons3d/Externals/pgengine/pgcommon/Externals/cocos2d2x',
- )
- def findproj(cwd=None):
- if cwd is None:
- cwd = os.getcwd()
- if cwd == '/':
- return '/Users/andrewbarnert/src/', 'dragons3d'
- for project in projects:
- if cwd.endswith(project):
- top = cwd[:-len(project)]
- return top, project
- return findproj(os.path.dirname(cwd))
- def repos(cwd=None):
- top, project = findproj(cwd)
- repo = git.Repo(os.path.join(top, project))
- others = [git.Repo(os.path.join(top, proj))
- for proj in projects if proj != project]
- return [repo] + others
- def commit_as_of(repo, timestamp):
- for commit in repo.iter_commits():
- if commit.committed_datetime < timestamp:
- return commit
- @click.group()
- def cli():
- pass
- @cli.command()
- @click.option("--checkout", is_flag=True)
- @click.argument('timestamp', default=None, required=False)
- def asof(checkout, timestamp):
- if timestamp:
- timestamp = dateutil.parser.parse(timestamp).astimezone()
- else:
- timestamp = datetime.datetime.now(datetime.timezone.utc)
- for repo in repos():
- dirname = os.path.basename(repo.working_dir)
- commit = commit_as_of(repo, timestamp)
- print(f'{dirname}: {commit} at {commit.committed_datetime}')
- if checkout:
- # TODO: Should we handle errors here and continue to the
- # other subtrees? (The reason to use git checkout rather
- # than just setting repo.head.reference is so that we _will_
- # get errors, instead of blowing away local copies.)
- repo.git.checkout(commit)
- if __name__ == '__main__':
- cli()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement