Skip to content

Commit

Permalink
Add create-patch command
Browse files Browse the repository at this point in the history
This is a synonym for `show -o json` at present, but the two will
diverge.

The intent is to add reprojection to spatial diffs, but we need a patch
creation command that does not support that.
  • Loading branch information
craigds committed Aug 17, 2020
1 parent e4982b2 commit 91551e5
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 27 deletions.
1 change: 1 addition & 0 deletions sno/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def cli(ctx, repo_dir, verbose, post_mortem):
cli.add_command(meta.meta)
cli.add_command(pull.pull)
cli.add_command(resolve.resolve)
cli.add_command(show.create_patch)
cli.add_command(show.show)
cli.add_command(status.status)
cli.add_command(query.query)
Expand Down
81 changes: 54 additions & 27 deletions sno/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,33 @@
EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"


def _get_parent(ctx, refish):
repo = ctx.obj.get_repo(allowed_states=RepoState.ALL_STATES)
# Ensures we were given a reference to a commit, and not a tree or something
commit = CommitWithReference.resolve(repo, refish).commit

try:
parents = commit.parents
except KeyError:
# one or more parents doesn't exist.
# This is okay if this is the first commit of a shallow clone.
# (how to tell?)
return EMPTY_TREE_SHA
else:
if parents:
return f"{refish}^"
else:
return EMPTY_TREE_SHA


@click.command()
@click.pass_context
@click.option(
"--output-format",
"-o",
type=click.Choice(["text", "json", "patch"]),
type=click.Choice(["text", "json"]),
default="text",
help="Output format. 'patch' is a synonym for 'json'",
help="Output format",
)
@click.option(
"--json-style",
Expand All @@ -33,30 +52,38 @@ def show(ctx, *, refish, output_format, json_style, **kwargs):
"""
Show the given commit, or HEAD
"""
if output_format == 'patch':
output_format = 'json'

repo = ctx.obj.get_repo(allowed_states=RepoState.ALL_STATES)
# Ensures we were given a reference to a commit, and not a tree or something
commit = CommitWithReference.resolve(repo, refish).commit
show_writer = globals()[f"show_output_{output_format}"]
parent = _get_parent(ctx, refish)
return diff.diff_with_writer(
ctx,
show_writer,
exit_code=False,
commit_spec=f"{parent}...{refish}",
filters=[],
json_style=json_style,
)

try:
parents = commit.parents
except KeyError:
# one or more parents doesn't exist.
# This is okay if this is the first commit of a shallow clone.
# (how to tell?)
parent = EMPTY_TREE_SHA
else:
if parents:
parent = f"{refish}^"
else:
parent = EMPTY_TREE_SHA
patch_writer = globals()[f"patch_output_{output_format}"]

@click.command(name='create-patch')
@click.pass_context
@click.option(
"--json-style",
type=click.Choice(["extracompact", "compact", "pretty"]),
default="pretty",
help="How to format the output",
)
# NOTE: this is *required* for now.
# A future version might create patches from working-copy changes.
@click.argument("refish")
def create_patch(ctx, *, refish, json_style, **kwargs):
"""
Creates a JSON patch from the given ref.
The patch can be applied with `sno apply`.
"""
parent = _get_parent(ctx, refish)
return diff.diff_with_writer(
ctx,
patch_writer,
show_output_json,
exit_code=False,
commit_spec=f"{parent}...{refish}",
filters=[],
Expand All @@ -65,7 +92,7 @@ def show(ctx, *, refish, output_format, json_style, **kwargs):


@contextlib.contextmanager
def patch_output_text(*, target, output_path, **kwargs):
def show_output_text(*, target, output_path, **kwargs):
"""
Contextmanager.
Expand Down Expand Up @@ -109,19 +136,19 @@ def patch_output_text(*, target, output_path, **kwargs):


@contextlib.contextmanager
def patch_output_json(*, target, output_path, json_style, **kwargs):
def show_output_json(*, target, output_path, json_style, **kwargs):
"""
Contextmanager.
Same arguments and usage as `patch_output_text`; see that docstring for usage.
Same arguments and usage as `show_output_text`; see that docstring for usage.
On exit, writes the patch as JSON to the given output file.
On exit, writes the output as JSON to the given output file.
If the output file is stdout and isn't piped anywhere,
the json is prettified first.
The patch JSON contains two top-level keys:
"sno.diff/v1+hexwkb": contains a JSON diff. See `sno.diff.diff_output_json` docstring.
"sno.patch/v1": contains metadata about the commit this patch represents:
"sno.patch/v1": contains metadata about the commit:
{
"authorEmail": "joe@example.com",
"authorName": "Joe Bloggs",
Expand Down
25 changes: 25 additions & 0 deletions tests/test_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,31 @@ def test_show_json_format(data_archive_readonly, cli_runner):
assert '"sno.diff/v1+hexwkb": {"' in r.stdout


@pytest.mark.parametrize(*V1_OR_V2)
def test_create_patch(structure_version, data_archive_readonly, cli_runner):
"""
Show a patch; ref defaults to HEAD
"""
data_archive = "points2" if structure_version == "2" else "points"
with data_archive_readonly(data_archive):
r = cli_runner.invoke(["create-patch"])
assert r.exit_code == 2, r.stderr
r = cli_runner.invoke(["create-patch", "HEAD"])
assert r.exit_code == 0, r.stderr

j = json.loads(r.stdout)
# check the diff's present, but this test doesn't need to have hundreds of lines
# to know exactly what it is (we have diff tests above)
assert 'sno.diff/v1+hexwkb' in j
assert j['sno.patch/v1'] == {
'authorEmail': 'robert@coup.net.nz',
'authorName': 'Robert Coup',
'authorTime': '2019-06-20T14:28:33Z',
'authorTimeOffset': '+01:00',
'message': 'Improve naming on Coromandel East coast',
}


def test_show_shallow_clone(data_archive_readonly, cli_runner, tmp_path, chdir):
# just checking you can 'show' the first commit of a shallow clone
with data_archive_readonly("points") as original_path:
Expand Down

0 comments on commit 91551e5

Please sign in to comment.