Skip to content

Commit 386aa5e

Browse files
jpichonmergify[bot]
authored andcommitted
Add convenience method for creating a branch
1 parent a1ee8f9 commit 386aa5e

File tree

4 files changed

+130
-17
lines changed

4 files changed

+130
-17
lines changed

git_wrapper/branch.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ def _run_cherry(self, upstream, head, regex):
4848
ret_data[match.group(1)] = match.group(2)
4949
return ret_data
5050

51+
@reference_exists("start_ref")
52+
def create(self, name, start_ref, reset_if_exists=False):
53+
"""Create a local branch based on start_ref.
54+
55+
If the branch exists, do nothing or hard reset it if reset_if_exists
56+
is set.
57+
58+
:param str name: New branch's name
59+
:param str start_ref: Reference (branch, commit, tag, ...) to use as
60+
a starting point.
61+
:param bool reset_if_exists: Whether to hard reset the branch to
62+
start_ref if the branch already exists.
63+
"""
64+
if not self.exists(name):
65+
self.git_repo.git.branch(name, start_ref)
66+
return True
67+
if self.exists(name) and not reset_if_exists:
68+
return
69+
if self.exists(name) and reset_if_exists:
70+
self.hard_reset_to_ref(name, start_ref)
71+
5172
def exists(self, name, remote=None):
5273
"""Checks if a branch exists locally or on the specified remote.
5374
@@ -279,7 +300,7 @@ def short_log_diff(self, hash_from, hash_to):
279300

280301
def hard_reset(self, branch="master", remote="origin",
281302
remote_branch="master", refresh=True):
282-
"""Perform a hard reset of a local branch.
303+
"""Perform a hard reset of a local branch to a remote branch.
283304
284305
:param str branch: Local branch to reset
285306
:param str remote: Remote use as base for the reset
@@ -289,7 +310,23 @@ def hard_reset(self, branch="master", remote="origin",
289310
if refresh:
290311
self.git_repo.remote.fetch(remote)
291312

292-
# Switch to branch
313+
remote_ref = "{0}/{1}".format(remote, remote_branch)
314+
self.hard_reset_to_ref(branch, remote_ref)
315+
316+
def hard_reset_to_ref(self, branch, ref):
317+
"""Perform a hard reset of a local branch to any reference.
318+
319+
:param str branch: Local branch to reset
320+
:param str ref: Reference (commit, tag, ...) to reset to
321+
"""
322+
# Ensure the reference maps to a commit
323+
try:
324+
commit = git.repo.fun.name_to_object(self.git_repo.repo, ref)
325+
except git.exc.BadName as ex:
326+
msg = "Could not find reference {0}.".format(ref)
327+
raise_from(exceptions.ReferenceNotFoundException(msg), ex)
328+
329+
# Switch to the branch
293330
try:
294331
self.git_repo.git.checkout(branch)
295332
except git.GitCommandError as ex:
@@ -299,25 +336,14 @@ def hard_reset(self, branch="master", remote="origin",
299336
)
300337
raise_from(exceptions.CheckoutException(msg), ex)
301338

302-
# Get a reference to the commit we want to reset to
303-
remote_ref = "{0}/{1}".format(remote, remote_branch)
304-
try:
305-
commit = git.repo.fun.name_to_object(self.git_repo.repo,
306-
remote_ref)
307-
except git.exc.BadName as ex:
308-
msg = "Could not find remote reference {0}.".format(remote_ref)
309-
raise_from(exceptions.ReferenceNotFoundException(msg), ex)
310-
311339
# Reset --hard to that reference
312340
try:
313341
self.git_repo.repo.head.reset(commit=commit,
314342
index=True,
315343
working_tree=True)
316344
except git.GitCommandError as ex:
317345
msg = (
318-
"Error resetting branch {branch} to {remote_ref}. "
319-
"Error: {error}".format(remote_ref=remote_ref,
320-
branch=branch,
321-
error=ex)
346+
"Error resetting branch {branch} to {ref}. "
347+
"Error: {error}".format(ref=ref, branch=branch, error=ex)
322348
)
323349
raise_from(exceptions.ResetException(msg), ex)

integration_tests/test_branch.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,27 @@ def test_reset(repo_root):
123123
# Ensure the new head matches the origin/master we saved
124124
branch_commit = repo.repo.branches[branch_name].commit
125125
assert branch_commit.hexsha == reset_to_commit.hexsha
126+
127+
128+
def test_create_branch(repo_root):
129+
repo = GitRepo(repo_root)
130+
branch_name = "test_create"
131+
tag_0_0_1_hexsha = "631b3a35723a038c01669e1933571693a166db81"
132+
tag_0_1_0_hexsha = "2e6c014bc296be90a7ed04d155ea7d9da2240bbc"
133+
134+
assert branch_name not in repo.repo.branches
135+
136+
# Create the new branch
137+
repo.branch.create(branch_name, "0.0.1")
138+
assert branch_name in repo.repo.branches
139+
assert repo.repo.branches[branch_name].commit.hexsha == tag_0_0_1_hexsha
140+
141+
# Branch already exists - do nothing
142+
repo.branch.create(branch_name, "0.1.0")
143+
assert branch_name in repo.repo.branches
144+
assert repo.repo.branches[branch_name].commit.hexsha == tag_0_0_1_hexsha
145+
146+
# Branch already exists - reset it
147+
repo.branch.create(branch_name, "0.1.0", True)
148+
assert branch_name in repo.repo.branches
149+
assert repo.repo.branches[branch_name].commit.hexsha == tag_0_1_0_hexsha

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ def mock_repo():
2121
remote.configure_mock(name="origin", url="http://example.com")
2222
remote_list = IterableList("name")
2323
remote_list.extend([remote])
24+
2425
repo_mock.remotes = remote_list
26+
repo_mock.branches = []
2527

2628
return repo_mock
2729

tests/test_branch.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,8 +658,9 @@ def test_reset_checkout_failure(mock_repo):
658658
mock_repo.git.checkout.side_effect = git.GitCommandError('checkout', '')
659659
repo = GitRepo(repo=mock_repo)
660660

661-
with pytest.raises(exceptions.CheckoutException):
662-
repo.branch.hard_reset(refresh=False)
661+
with patch('git.repo.fun.name_to_object'):
662+
with pytest.raises(exceptions.CheckoutException):
663+
repo.branch.hard_reset(refresh=False)
663664

664665
assert mock_repo.head.reset.called is False
665666

@@ -743,3 +744,63 @@ def test_remote_branch_doesnt_exists(mock_repo):
743744
mock_repo.remotes.extend([remote])
744745

745746
assert repo.branch.exists("testbranch", "testremote") is False
747+
748+
749+
def test_create_branch(mock_repo):
750+
"""
751+
GIVEN GitRepo is initialized with a path and repo
752+
WHEN branch.create is called with a valid name and start_ref
753+
THEN git.branch is called
754+
"""
755+
repo = GitRepo(repo=mock_repo)
756+
757+
with patch('git.repo.fun.name_to_object'):
758+
assert repo.branch.create("test", "123456") is True
759+
repo.git.branch.assert_called_with("test", "123456")
760+
761+
762+
def test_create_branch_with_bad_start_ref(mock_repo):
763+
"""
764+
GIVEN GitRepo is initialized with a path and repo
765+
WHEN branch.create is called with a valid name and invalid start_ref
766+
THEN a ReferenceNotFoundException is raised
767+
"""
768+
repo = GitRepo(repo=mock_repo)
769+
770+
with patch('git.repo.fun.name_to_object') as mock_name_to_object:
771+
mock_name_to_object.side_effect = git.exc.BadName()
772+
with pytest.raises(exceptions.ReferenceNotFoundException):
773+
assert repo.branch.create("test", "badref")
774+
775+
776+
def test_create_branch_already_exists(mock_repo):
777+
"""
778+
GIVEN GitRepo is initialized with a path and repo
779+
WHEN branch.create is called with a valid name and start_ref
780+
AND the branch already exists
781+
THEN git.branch is not called
782+
"""
783+
repo = GitRepo(repo=mock_repo)
784+
mock_repo.branches = ["test", "master"]
785+
786+
with patch('git.repo.fun.name_to_object'):
787+
repo.branch.create("test", "123456")
788+
assert repo.git.branch.called is False
789+
790+
791+
def test_create_branch_already_exists_and_reset_it(mock_repo):
792+
"""
793+
GIVEN GitRepo is initialized with a path and repo
794+
WHEN branch.create is called with a valid name and start_ref
795+
AND the branch already exists and reset_if_exists is True
796+
THEN hard_reset_to_ref is called
797+
"""
798+
repo = GitRepo(repo=mock_repo)
799+
mock_repo.branches = ["test", "master"]
800+
801+
mock_hard_reset = Mock()
802+
repo.branch.hard_reset_to_ref = mock_hard_reset
803+
804+
with patch('git.repo.fun.name_to_object'):
805+
repo.branch.create("test", "123456", True)
806+
assert mock_hard_reset.called is True

0 commit comments

Comments
 (0)