Skip to content
Open
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
16 changes: 16 additions & 0 deletions branch_compare/.gitmastery-exercise.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"exercise_name": "branch-compare",
"tags": ["git-branch", "git-diff"],
"requires_git": true,
"requires_github": false,
"base_files": {
"answers.txt": "answers.txt"
},
"exercise_repo": {
"repo_type": "local",
"repo_name": "data_streams",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"repo_name": "data_streams",
"repo_name": "data-streams",

"repo_title": null,
"create_fork": null,
"init": true
}
}
9 changes: 9 additions & 0 deletions branch_compare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# branch-compare

## Task

You are recording a numerical data stream from two sources. The data are stored in `data.txt`, using a different branch for each stream. The two data streams are supposed to be identical but can vary on rare occasions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should be above Task


Answer the questions given in `answers.txt`.

Run `gitmastery verify` to check if your answers are correct.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Run `gitmastery verify` to check if your answers are correct.

We can remove this line

Empty file added branch_compare/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions branch_compare/download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from exercise_utils.cli import run_command
from exercise_utils.git import add, commit, checkout
from exercise_utils.file import append_to_file
from exercise_utils.gitmastery import create_start_tag

import random

__resources__ = {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__resources__ = {}

This can be removed if not used


def get_sequence(n=1000, digits=8, seed=None):
rng = random.Random(seed)
lo, hi = 10**(digits - 1), 10**digits - 1
return rng.sample(range(lo, hi + 1), k=n)

def get_modified_sequence(seq, digits=8, idx=None, seed=None):
rng = random.Random(seed)
n = len(seq)
if idx is None:
idx = rng.randrange(n)

modified = seq.copy()
seen = set(seq)
lo, hi = 10**(digits - 1), 10**digits - 1

old = modified[idx]
new = old
while new in seen:
new = rng.randint(lo, hi)
modified[idx] = new
return modified


def setup(verbose: bool = False):

create_start_tag(verbose)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
create_start_tag(verbose)

This can be removed as not needed here


orig_data = get_sequence()
modified_data = get_modified_sequence(orig_data)

run_command(["touch", "data.txt"], verbose)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer to use create_or_update_file here

Suggested change
run_command(["touch", "data.txt"], verbose)
create_or_update_file("data.txt", "")

add(["data.txt"], verbose)
commit("Add empty data.txt", verbose)
checkout("stream-1", True, verbose)

for i in orig_data:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it would be more efficient to create the entire string first using join, then append only once to the file to prevent too many OS syscalls

append_to_file("data.txt", str(i)+"\n")

add(["data.txt"], verbose)
commit("Add data to data.txt", verbose)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change


checkout("main", False, verbose)
checkout("stream-2", True, verbose)

for i in modified_data:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

append_to_file("data.txt", str(i)+"\n")

add(["data.txt"], verbose)
commit("Add data to data.txt", verbose)

checkout("main", False, verbose)

5 changes: 5 additions & 0 deletions branch_compare/res/answers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Q: Which number (write only one number) is present in branch stream-1 but not in branch stream-2?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Q: Which number (write only one number) is present in branch stream-1 but not in branch stream-2?
Q: Which numbers are present in stream-1 but not in stream-2?

Should align with questions in issue

A:

Q: Which number (write only one number) is present in branch stream-2 but not in branch stream-1?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Q: Which number (write only one number) is present in branch stream-2 but not in branch stream-1?
Q: Which numbers are present in stream-2 but not in stream-1?

A:
Empty file.
44 changes: 44 additions & 0 deletions branch_compare/tests/specs/base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
initialization:
steps:
- type: commit
empty: true
message: Empty commit
id: start

# Create stream-1 with data.txt containing a unique number 12345
- type: branch
branch-name: stream-1
- type: new-file
filename: data.txt
contents: |
11111
22222
12345
- type: add
files:
- data.txt
- type: commit
message: Add data.txt on stream-1

# Return to main
- type: checkout
branch-name: main

# Create stream-2 with data.txt containing a unique number 98765
- type: branch
branch-name: stream-2
- type: new-file
filename: data.txt
contents: |
11111
22222
98765
- type: add
files:
- data.txt
- type: commit
message: Add data.txt on stream-2

# Return to main at the end for verify to start cleanly
- type: checkout
branch-name: main
55 changes: 55 additions & 0 deletions branch_compare/tests/specs/extra_commit_on_stream1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
initialization:
steps:
- type: commit
empty: true
message: Empty commit
id: start

# Base setup identical to base.yml
- type: branch
branch-name: stream-1
- type: new-file
filename: data.txt
contents: |
11111
22222
12345
- type: add
files:
- data.txt
- type: commit
message: Add data.txt on stream-1

- type: checkout
branch-name: main

- type: branch
branch-name: stream-2
- type: new-file
filename: data.txt
contents: |
11111
22222
98765
- type: add
files:
- data.txt
- type: commit
message: Add data.txt on stream-2

# Make an extra change on stream-1 to simulate user changes
- type: checkout
branch-name: stream-1
- type: new-file
filename: extra.txt
contents: |
extra content
- type: add
files:
- extra.txt
- type: commit
message: Extra change on stream-1

# Return to main at the end
- type: checkout
branch-name: main
55 changes: 55 additions & 0 deletions branch_compare/tests/test_verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from git_autograder import GitAutograderStatus, GitAutograderTestLoader, assert_output
from git_autograder.answers.rules.has_exact_value_rule import HasExactValueRule

from ..verify import verify, QUESTION_ONE, QUESTION_TWO, NO_CHANGES_ERROR

REPOSITORY_NAME = "branch-compare"

loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify)


def test_base():
with loader.load(
"specs/base.yml",
"start",
mock_answers={
QUESTION_ONE: "12345",
QUESTION_TWO: "98765",
},
) as output:
assert_output(output, GitAutograderStatus.SUCCESSFUL)

def test_wrong_stream1_diff():
with loader.load(
"specs/base.yml",
"start",
mock_answers={
QUESTION_ONE: "99999",
QUESTION_TWO: "98765",
},
) as output:
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[HasExactValueRule.NOT_EXACT.format(question=QUESTION_ONE)],
)

def test_wrong_stream2_diff():
with loader.load(
"specs/base.yml",
"start",
mock_answers={
QUESTION_ONE: "12345",
QUESTION_TWO: "99999",
},
) as output:
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO)],
)


def test_changes_made_extra_commit():
with loader.load("specs/extra_commit_on_stream1.yml", "start") as output:
assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_CHANGES_ERROR])
83 changes: 83 additions & 0 deletions branch_compare/verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from git_autograder import (
GitAutograderOutput,
GitAutograderExercise,
GitAutograderStatus,
)

from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule


QUESTION_ONE = "Which number (write only one number) is present in branch stream-1 but not in branch stream-2?"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
QUESTION_ONE = "Which number (write only one number) is present in branch stream-1 but not in branch stream-2?"
QUESTION_ONE = "Which numbers are present in stream-1 but not in stream-2?"

QUESTION_TWO = "Which number (write only one number) is present in branch stream-2 but not in branch stream-1?"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
QUESTION_TWO = "Which number (write only one number) is present in branch stream-2 but not in branch stream-1?"
QUESTION_TWO = "Which numbers are present in stream-2 but not in stream-1?"

NO_CHANGES_ERROR = "No changes are supposed to be made to the two branches in this exercise"

FILE_PATH = "data.txt"
BRANCH_1 = "stream-1"
BRANCH_2 = "stream-2"

def has_made_changes(exercise: GitAutograderExercise) -> bool:
repo = exercise.repo.repo

for bname in (BRANCH_1, BRANCH_2):
if not exercise.repo.branches.has_branch(bname):
return True

head = repo.commit(bname)

if len(head.parents) != 1:
return True

# Count commits unique to branch relative to main
merge_bases = repo.merge_base(bname, "main")
if not merge_bases:
return True
base = merge_bases[0]
unique_commits = list(repo.iter_commits(f"{base.hexsha}..{bname}"))
if len(unique_commits) != 1:
return True

return False

def get_branch_diff(exercise: GitAutograderExercise, branch1: str, branch2: str) -> str:
exercise.repo.branches.branch(branch1).checkout()
with exercise.repo.files.file(FILE_PATH) as f1:
contents1 = f1.read()

exercise.repo.branches.branch(branch2).checkout()
with exercise.repo.files.file(FILE_PATH) as f2:
contents2 = f2.read()

exercise.repo.branches.branch("main").checkout()

set1 = {line.strip() for line in contents1.splitlines() if line.strip()}
set2 = {line.strip() for line in contents2.splitlines() if line.strip()}
diff = set1 - set2
return str(diff.pop())

def get_stream1_diff(exercise: GitAutograderExercise) -> str:
return get_branch_diff(exercise, BRANCH_1, BRANCH_2)

def get_stream2_diff(exercise: GitAutograderExercise) -> str:
return get_branch_diff(exercise, BRANCH_2, BRANCH_1)

def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:

if has_made_changes(exercise):
return exercise.to_output([NO_CHANGES_ERROR], GitAutograderStatus.UNSUCCESSFUL)

exercise.repo.branches.branch("main").checkout()

ans_1 = get_stream1_diff(exercise)
ans_2 = get_stream2_diff(exercise)

exercise.answers.add_validation(
QUESTION_ONE,
NotEmptyRule(),
HasExactValueRule(ans_1),
).add_validation(
QUESTION_TWO,
NotEmptyRule(),
HasExactValueRule(ans_2),
).validate()

return exercise.to_output(["Great work comparing the branches successfully!"], GitAutograderStatus.SUCCESSFUL)