Skip to content

Commit

Permalink
Allow playback of patchfiles (wrapper for git am)
Browse files Browse the repository at this point in the history
  • Loading branch information
viktordick authored and Viktor Dick committed Jun 7, 2019
1 parent 71787e1 commit 805ecdb
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
3.12.4
Provide a wrapper to apply patches and play back the affected objects.

3.12.3
Fix python3 crash in read_pdata, source might be bytes or string.

Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,16 @@ marked as `unsupported`, which are ignored if found in the ZODB. If only a
given object itself should be updated (properties, security settings etc.),
`--no-recurse` can be used.

If using `--pick`, the given paths are not interpreted as object paths, but as
git commits. This is useful if the repository the Data.FS is recorded to is
tracked by git and some development has been done on a remote system that has
to be deployed to the current system. It then becomes possible to do something
like
There are two other modes to use with `perfact-zopeplayback`, selected by
passing `--pick` or `--apply`. These assume the file system representation is
stored in a git repository and provide wrappers for `git cherry-pick` and `git
am`, respectively. They also change the interpretation of the positional `path`
arguments.

If using `--pick`, the given paths are interpreted as git commits. This is
useful if some development has been done in a branch or on a remote system that
has to be deployed to the current system. It then becomes possible to do
something like

git fetch origin
perfact-zopeplayback --pick origin/master
Expand All @@ -127,6 +132,10 @@ for example, to pull all commits where the commit message starts with T12345:

perfact-zopeplayback --pick $(git log origin/master --reverse --pretty=%H --grep="^T12345" )


Similarly, `--apply` allows to pass patch files that are to be applied, playing
back all objects that are changed by these patches.

## Compatibility
This package aims to replace similar functionality that was previously found in
python-perfact and perfact-dbutils-zope2. For backwards compatibility, those
Expand Down
84 changes: 65 additions & 19 deletions bin/perfact-zopeplayback
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,31 @@ if __name__ == '__main__':
'--skip-errors', action='store_true',
help="Skip failed objects and continue",
default=False)
parser.add_argument(
group = parser.add_mutually_exclusive_group()
group.add_argument(
'--pick', action='store_true',
help='''
Switch to cherry-pick mode. The positional arguments are then
interpreted as commits, which are checked for compatibility and
applied, playing back all affected paths at the end.
''',
)
group.add_argument(
'--apply', action='store_true',
help='''
Switch to apply mode. The positional arguments are then interpreted as
patch files which are applied to the repository (using git am). If
successful, the changed objects are played back. Else, the am session
is automatically rolled back.
''',
)

parser.add_argument(
'path', nargs='+', type=str,
help='''
In default mode, paths of Data.FS objects to be played back. With
--pick, commits to be applied. The object list is then determined
automatically.
--pick, commits to be applied. With --apply, patch files to be applied.
The object list is then determined automatically.
''',
)
if 'perfact.loggingtools' in sys.modules:
Expand All @@ -71,16 +82,17 @@ if __name__ == '__main__':
recurse = not args.no_recurse

pickmode = args.pick
applymode = args.apply

# Setup sync toolkit
sync = perfact.zodbsync.ZODBSync(conffile=args.config, logger=logger)

sync.acquire_lock()

def gitcmd(*args):
return ['git', '-C', sync.base_dir] + list(args)
if pickmode or applymode:
def gitcmd(*args):
return ['git', '-C', sync.base_dir] + list(args)

if pickmode:
# Check that there are no unstaged changes
dirty_workdir = len(subprocess.check_output(
gitcmd('status', '--porcelain'),
Expand All @@ -89,15 +101,51 @@ if __name__ == '__main__':
print("You have unstaged changes. Please commit or stash them")
sys.exit(1)

commits = paths
paths = set()

orig_commit = subprocess.check_output(
gitcmd('show-ref', '--head', 'HEAD'),
universal_newlines=True
).split()[0]
print('Original commit: %s' % orig_commit)

changed_files = set()

# compatibility since python2 has no subprocess.DEVNULL
fnull = open(os.devnull, 'w')

# recurse and override are set automatically for these modes
recurse = False
override = True

if applymode:
# somehow, if we pass relative filenames to git while it is called with
# '-C' or even with '--git-dir' and '--work-tree', they are no longer
# interpreted relative to the current work dir. Therefore, we open the
# files ourselves and pass them via stdin
proc = subprocess.Popen(gitcmd('am'),
stdin=subprocess.PIPE
)
for path in paths:
print("Reading from %s" % path)
with open(path) as f:
proc.stdin.write(f.read())

proc.stdin.close()
proc.wait()
if proc.returncode:
print('Error applying patches. Rolling back')
subprocess.call(gitcmd('am', '--abort'))
sys.exit(1)

changed_files.update(set(subprocess.check_output(
gitcmd('diff-tree', '--no-commit-id', '--name-only', '-r',
orig_commit, 'HEAD'),
universal_newlines=True,
).strip().split('\n')))

if pickmode:
commits = paths
paths = set()

for commit in commits:
print('Checking and applying %s.' % commit)
# obtain files affected by the commit
Expand All @@ -122,27 +170,25 @@ if __name__ == '__main__':
subprocess.check_call(gitcmd('reset', '--hard', orig_commit))
sys.exit(1)

paths.update({
filename[len('__root__'):].rsplit('/', 1)[0]
for filename in files
})
# compatibility since python2 has no subprocess.DEVNULL
fnull = open(os.devnull, 'w')
changed_files.update(files)
subprocess.check_call(
gitcmd('cherry-pick', commit),
stdout=fnull
)

paths = sorted(paths)
recurse = False
override = True
if applymode or pickmode:
paths = sorted({
filename[len('__root__'):].rsplit('/', 1)[0]
for filename in changed_files
})
else:
paths.sort()

note = 'perfact-zopeplayback'
if len(paths) == 1:
note += ': ' + paths[0]
txn_mgr = sync.start_transaction(note=note)

paths.sort()
try:
for path in paths:
sync.playback(path=path,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from setuptools import setup
setup(name='perfact-zodbsync',
version='3.12.3',
version='3.12.4',
description='Zope Recorder and Playback',
long_description=''' ''',
author='Ján Jockusch et.al.',
Expand Down

0 comments on commit 805ecdb

Please sign in to comment.