Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add create-patch command #210

Merged
merged 1 commit into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ _When adding new entries to the changelog, please include issue/PR numbers where

### Other changes in this release

* Added `sno create-patch <refish>` - creates a JSON patch file, which can be applied using `sno apply` [#210](https://github.com/koordinates/sno/issues/210)
* `sno clone` now support shallow clones (`--depth N`) to avoid cloning a repo's entire history [#174](https://github.com/koordinates/sno/issues/174)
* `sno log` now supports JSON output with `--output-format json` [#170](https://github.com/koordinates/sno/issues/170)
* Streaming diffs: less time until first change is shown when diffing large changes. [#156](https://github.com/koordinates/sno/issues/156)
Expand Down
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