You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by je...@apache.org on 2015/02/19 17:25:49 UTC
[1/3] allura git commit: [#7830] ticket:729 One-click merge for git
Repository: allura
Updated Branches:
refs/heads/ib/7830 [created] e37f9437e
[#7830] ticket:729 One-click merge for git
Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/4d38103c
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/4d38103c
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/4d38103c
Branch: refs/heads/ib/7830
Commit: 4d38103cb7096a0265e930b250924d57c2a39003
Parents: a3620b4
Author: Igor Bondarenko <je...@gmail.com>
Authored: Wed Feb 18 17:00:27 2015 +0000
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Thu Feb 19 10:43:36 2015 +0000
----------------------------------------------------------------------
Allura/allura/controllers/repository.py | 13 ++++++++
Allura/allura/model/repository.py | 24 +++++++++++++++
Allura/allura/templates/repo/merge_request.html | 26 ++++++++++++++++
ForgeGit/forgegit/model/git_repo.py | 32 ++++++++++++++++++++
4 files changed, 95 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/allura/blob/4d38103c/Allura/allura/controllers/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index 1751839..a2f888d 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -439,6 +439,19 @@ class MergeRequestController(object):
self.req.status = status
redirect('.')
+ @expose()
+ @require_post()
+ def merge(self):
+ require_access(c.app, 'write')
+ if self.req.status != 'open' or not self.req.can_merge():
+ raise exc.HTTPNotFound
+ ok = self.req.merge()
+ if ok:
+ flash('Merged successfully', 'ok')
+ else:
+ flash('Merge failed. Please, merge manually', 'error')
+ redirect(self.req.url())
+
class RefsController(object):
http://git-wip-us.apache.org/repos/asf/allura/blob/4d38103c/Allura/allura/model/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 0030aa5..5e4a171 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -805,6 +805,30 @@ class MergeRequest(VersionedArtifact, ActivityObject):
self.request_number, self.project.name, self.app.repo.name))
return result
+ def can_merge(self):
+ if not self.app.forkable:
+ return False
+ try:
+ result = self.app.repo.can_merge(self)
+ except:
+ log.exception(
+ "Can't determine if merge request %s can be merged",
+ self.url())
+ return False
+ return result
+
+ def merge(self):
+ if not self.app.forkable:
+ return False
+ try:
+ self.app.repo.merge(self)
+ except:
+ log.exception("Can't merge merge request %s", self.url())
+ return False
+ self.status = 'merged'
+ session(self).flush(self)
+ return True
+
# Basic commit information
# One of these for each commit in the physical repo on disk. The _id is the
http://git-wip-us.apache.org/repos/asf/allura/blob/4d38103c/Allura/allura/templates/repo/merge_request.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/repo/merge_request.html b/Allura/allura/templates/repo/merge_request.html
index 3c79654..060b5b9 100644
--- a/Allura/allura/templates/repo/merge_request.html
+++ b/Allura/allura/templates/repo/merge_request.html
@@ -44,6 +44,25 @@ Merge Request #{{req.request_number}}: {{req.summary}} ({{req.status}})
<div>{{g.markdown.convert(req.description)}}</div>
+ {% if req.status == 'open' and c.app.forkable and h.has_access(c.app, 'write')() %}
+ {% set can_merge = req.can_merge() %}
+ <div class="grid-19">
+ <form action="merge" method="POST">
+ {{ lib.csrf_token() }}
+ <input type="submit" value="Merge"{% if not can_merge %}disabled="disabled"{% endif %}>
+ {% if can_merge %}
+ <div class="merge-ok">
+ Merge request has no conflicts. You can merge automatically.
+ </div>
+ {% else %}
+ <div class="merge-conflicts">
+ Merge request has conflicts. Follow manual instructions below to merge.
+ </div>
+ {% endif %}
+ </form>
+ </div>
+ {% endif %}
+
{{ c.log_widget.display(value=req.commits, app=downstream_app) }}
<div class="grid-19"><a href="#discussion_holder">Discuss</a></div>
@@ -87,3 +106,10 @@ Merge Request #{{req.request_number}}: {{req.summary}} ({{req.status}})
count=count)}}
</div>
{% endblock %}
+
+{% block extra_css %}
+<style type="text/css">
+ .merge-ok { color: green; }
+ .merge-conflicts { color: red; }
+</style>
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/allura/blob/4d38103c/ForgeGit/forgegit/model/git_repo.py
----------------------------------------------------------------------
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index 7fda67a..831da5b 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -19,6 +19,7 @@ import os
import shutil
import string
import logging
+import tempfile
from datetime import datetime
import tg
@@ -95,6 +96,37 @@ class Repository(M.Repository):
merge_request.downstream.commit_id,
)
+ def can_merge(self, mr):
+ """
+ Given merge request `mr` determine if it can be merged w/o conflicts.
+ """
+ g = self._impl._git.git
+ # http://stackoverflow.com/a/6283843
+ # fetch source branch
+ g.fetch(mr.downstream_repo_url, mr.source_branch)
+ # find merge base
+ merge_base = g.merge_base(mr.downstream.commit_id, mr.target_branch)
+ # print out merge result, but don't actually touch anything
+ merge_tree = g.merge_tree(
+ merge_base, mr.target_branch, mr.downstream.commit_id)
+ return '+<<<<<<<' not in merge_tree
+
+ def merge(self, mr):
+ g = self._impl._git.git
+ # can't merge in bare repo, so need to clone
+ tmp_path = tempfile.mkdtemp()
+ tmp_repo = git.Repo.clone_from(
+ self.clone_url('rw'),
+ to_path=tmp_path,
+ bare=False)
+ tmp_repo = GitImplementation(Object(full_fs_path=tmp_path))._git
+ tmp_repo.git.fetch('origin', mr.target_branch)
+ tmp_repo.git.checkout(mr.target_branch)
+ tmp_repo.git.fetch(mr.downstream_repo_url, mr.source_branch)
+ tmp_repo.git.merge(mr.downstream.commit_id)
+ tmp_repo.git.push('origin', mr.target_branch)
+ shutil.rmtree(tmp_path, ignore_errors=True)
+
def rev_to_commit_id(self, rev):
return self._impl.rev_parse(rev).hexsha
[2/3] allura git commit: [#7830] ticket:729 Move actual merge to
background task
Posted by je...@apache.org.
[#7830] ticket:729 Move actual merge to background task
Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/8263d331
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/8263d331
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/8263d331
Branch: refs/heads/ib/7830
Commit: 8263d3313fe42689fa2c35c49021549e7b44ef63
Parents: 4d38103
Author: Igor Bondarenko <je...@gmail.com>
Authored: Thu Feb 19 12:14:52 2015 +0000
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Thu Feb 19 13:53:00 2015 +0000
----------------------------------------------------------------------
Allura/allura/controllers/repository.py | 11 +--
Allura/allura/model/repository.py | 27 ++++---
Allura/allura/tasks/repo_tasks.py | 15 ++++
Allura/allura/templates/repo/merge_request.html | 82 +++++++++++++++++---
4 files changed, 107 insertions(+), 28 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/allura/blob/8263d331/Allura/allura/controllers/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index a2f888d..ceb9035 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -364,6 +364,7 @@ class MergeRequestController(object):
return dict(
downstream_app=downstream_app,
req=self.req,
+ status=self.req.merge_task_status(),
page=page,
limit=limit,
count=self.req.discussion_thread.post_count)
@@ -445,13 +446,13 @@ class MergeRequestController(object):
require_access(c.app, 'write')
if self.req.status != 'open' or not self.req.can_merge():
raise exc.HTTPNotFound
- ok = self.req.merge()
- if ok:
- flash('Merged successfully', 'ok')
- else:
- flash('Merge failed. Please, merge manually', 'error')
+ self.req.merge()
redirect(self.req.url())
+ @expose('json:')
+ def merge_task_status(self):
+ return {'status': self.req.merge_task_status()}
+
class RefsController(object):
http://git-wip-us.apache.org/repos/asf/allura/blob/8263d331/Allura/allura/model/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 5e4a171..d866598 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -23,7 +23,7 @@ import string
import re
from subprocess import Popen, PIPE
from hashlib import sha1
-from datetime import datetime
+from datetime import datetime, timedelta
from time import time
from collections import defaultdict, OrderedDict
from urlparse import urljoin
@@ -818,16 +818,21 @@ class MergeRequest(VersionedArtifact, ActivityObject):
return result
def merge(self):
- if not self.app.forkable:
- return False
- try:
- self.app.repo.merge(self)
- except:
- log.exception("Can't merge merge request %s", self.url())
- return False
- self.status = 'merged'
- session(self).flush(self)
- return True
+ in_progress = self.merge_task_status() in ['ready', 'busy']
+ if self.app.forkable and not in_progress:
+ from allura.tasks import repo_tasks
+ repo_tasks.merge.post(self._id)
+
+ def merge_task_status(self):
+ task = MonQTask.query.find({
+ 'state': {'$in': ['busy', 'complete', 'error', 'ready']}, # needed to use index
+ 'task_name': 'allura.tasks.repo_tasks.merge',
+ 'args': [self._id],
+ 'time_queue': {'$gt': datetime.utcnow() - timedelta(days=1)}, # constrain on index further
+ }).sort('_id', -1).limit(1).first()
+ if task:
+ return task.state
+ return None
# Basic commit information
http://git-wip-us.apache.org/repos/asf/allura/blob/8263d331/Allura/allura/tasks/repo_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index 61cac83..e50f9d2 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -20,6 +20,7 @@ import logging
import traceback
from pylons import tmpl_context as c, app_globals as g
+from ming.odm import session
from allura.lib.decorators import task
from allura.lib.repository import RepositoryApp
@@ -152,3 +153,17 @@ def tarball(revision, path):
log.warn(
'Skipped creation of snapshot: %s:%s because revision is not specified' %
(c.project.shortname, c.app.config.options.mount_point))
+
+
+@task
+def merge(merge_request_id):
+ from allura import model as M
+ log = logging.getLogger(__name__)
+ mr = M.MergeRequest.query.get(_id=merge_request_id)
+ try:
+ mr.app.repo.merge(mr)
+ except:
+ log.exception("Can't merge merge request %s", mr.url())
+ return
+ mr.status = 'merged'
+ session(mr).flush(mr)
http://git-wip-us.apache.org/repos/asf/allura/blob/8263d331/Allura/allura/templates/repo/merge_request.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/repo/merge_request.html b/Allura/allura/templates/repo/merge_request.html
index 060b5b9..1520270 100644
--- a/Allura/allura/templates/repo/merge_request.html
+++ b/Allura/allura/templates/repo/merge_request.html
@@ -33,6 +33,19 @@ Merge Request #{{req.request_number}}: {{req.summary}} ({{req.status}})
{% endblock %}
{% block content %}
+ <div class="grid-19">
+ <div id="task_status">
+ {% if status == 'complete' %}
+ <h2 class="complete">Merged</h2>
+ {% else %}
+ <img src="{{g.forge_static('images/spinner.gif')}}" class="spinner" style="display:none"/>
+ <h2 class="busy ready">Merging...</h2>
+ <h2 class="complete">Merged</h2>
+ <h2 class="fail">Something went wrong. Please, merge manually</h2>
+ {% endif %}
+ </div>
+ </div>
+
{% if req.downstream_repo %}
<p>
<a href="{{req.creator_url}}">{{req.creator_name}}</a>
@@ -49,7 +62,7 @@ Merge Request #{{req.request_number}}: {{req.summary}} ({{req.status}})
<div class="grid-19">
<form action="merge" method="POST">
{{ lib.csrf_token() }}
- <input type="submit" value="Merge"{% if not can_merge %}disabled="disabled"{% endif %}>
+ <input type="submit" value="Merge"{% if not can_merge or status in ('ready', 'busy') %}disabled="disabled"{% endif %}>
{% if can_merge %}
<div class="merge-ok">
Merge request has no conflicts. You can merge automatically.
@@ -68,14 +81,16 @@ Merge Request #{{req.request_number}}: {{req.summary}} ({{req.status}})
<div class="grid-19"><a href="#discussion_holder">Discuss</a></div>
{% if h.has_access(c.app, 'write')() %}
- <div class="grid-19">To merge the commits, please execute the following commands in your working
- copy: </div>
- <div class="grid-19"><textarea
- style="width:80%; height:60px;"
- readonly
- >{{ c.app.repo.merge_command(req) | safe }}</textarea></div>
- {{ c.mr_dispose_form.display(action="save", value=dict(status=req.status)) }}
- <br style="clear:both">
+ <div class="grid-19">
+ To merge the commits, please execute the following commands in your working copy:
+ </div>
+ <div class="grid-19">
+ <textarea style="width:80%; height:60px;" readonly>{{ c.app.repo.merge_command(req) | safe }}</textarea>
+ </div>
+ {% if status not in ('ready', 'busy') %}
+ {{ c.mr_dispose_form.display(action="save", value=dict(status=req.status)) }}
+ <br style="clear:both">
+ {% endif %}
{% endif %}
{% else %}
<p>
@@ -83,12 +98,10 @@ Merge Request #{{req.request_number}}: {{req.summary}} ({{req.status}})
<a href="{{req.creator_url}}">{{req.creator_name}}</a>
is deleted
</p>
-
<div>{{g.markdown.convert(req.description)}}</div>
-
{% if h.has_access(c.app, 'write')() %}
{{ c.mr_dispose_form.display(action="save", value=dict(status=req.status)) }}
- <br style="clear:both">
+ <br style="clear:both">
{% endif %}
{% endif %}
@@ -111,5 +124,50 @@ Merge Request #{{req.request_number}}: {{req.summary}} ({{req.status}})
<style type="text/css">
.merge-ok { color: green; }
.merge-conflicts { color: red; }
+
+ #task_status { margin: 0 10px; }
+ #task_status h2 { display: none; }
+ #task_status .{{ status }} { display: inline-block; }
+ #task_status h2.complete { color: #C6D880; }
+ #task_status h2.busy, #task_status h2.busy { color: #003565; }
+ #task_status h2.fail { color: #f33; }
</style>
{% endblock %}
+
+{% block extra_js %}
+{{ super() }}
+<script type="text/javascript">
+$(function() {
+ {% if status in ('ready', 'busy') %}
+ $('.spinner').show();
+ var delay = 500;
+ function check_status() {
+ $.get("{{request.path.rstrip('/') + '/merge_task_status'}}", function(data) {
+ if (data.status === 'complete') {
+ $('.spinner').hide();
+ $('#task_status h2').hide();
+ $('#task_status h2.complete').show();
+ location.reload();
+ } else {
+ if (data.status === 'ready' || data.status === 'busy') {
+ // keep waiting
+ $('#task_status h2').hide();
+ $('#task_status h2.busy').show();
+ } else {
+ // something went wrong
+ $('.spinner').hide();
+ $('#task_status h2').hide();
+ $('#task_status h2.fail').show();
+ }
+ if (delay < 60000){
+ delay = delay * 2;
+ }
+ window.setTimeout(check_status, delay);
+ }
+ });
+ }
+ window.setTimeout(check_status, delay);
+ {% endif %}
+});
+</script>
+{% endblock %}
[3/3] allura git commit: [#7830] ticket:729 Add tests
Posted by je...@apache.org.
[#7830] ticket:729 Add tests
Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/e37f9437
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/e37f9437
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/e37f9437
Branch: refs/heads/ib/7830
Commit: e37f9437ea40922ba31cefd6a4096b43c9a7cbb4
Parents: 8263d33
Author: Igor Bondarenko <je...@gmail.com>
Authored: Thu Feb 19 15:53:48 2015 +0000
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Thu Feb 19 15:53:48 2015 +0000
----------------------------------------------------------------------
Allura/allura/tests/model/test_repo.py | 38 ++++++++++++++++
Allura/allura/tests/test_tasks.py | 11 +++++
.../forgegit/tests/model/test_repository.py | 46 ++++++++++++++++++++
3 files changed, 95 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/allura/blob/e37f9437/Allura/allura/tests/model/test_repo.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 9e2af07..669f940 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -713,3 +713,41 @@ class TestModelCache(unittest.TestCase):
session.assert_called_once_with(tree1)
session.return_value.flush.assert_called_once_with(tree1)
session.return_value.expunge.assert_called_once_with(tree1)
+
+
+class TestMergeRequest(object):
+
+ def setUp(self):
+ setup_basic_test()
+ setup_global_objects()
+ self.mr = M.MergeRequest(app_config=mock.Mock(_id=ObjectId()))
+ self.mr.app = mock.Mock(forkable=True)
+
+ def test_can_merge(self):
+ assert_equal(self.mr.can_merge(),
+ self.mr.app.repo.can_merge.return_value)
+ self.mr.app.repo.can_merge.assert_called_once_with(self.mr)
+
+ self.mr.app.reset_mock()
+ self.mr.app.forkable = False
+ assert_equal(self.mr.can_merge(), False)
+ assert_equal(self.mr.app.repo.can_merge.called, False)
+
+ @mock.patch('allura.tasks.repo_tasks.merge', autospec=True)
+ def test_merge(self, merge_task):
+ self.mr.merge_task_status = lambda: None
+ self.mr.merge()
+ merge_task.post.assert_called_once_with(self.mr._id)
+
+ merge_task.reset_mock()
+ self.mr.merge_task_status = lambda: 'ready'
+ self.mr.merge()
+ assert_equal(merge_task.post.called, False)
+
+ def test_merge_task_status(self):
+ from allura.tasks import repo_tasks
+ assert_equal(self.mr.merge_task_status(), None)
+ repo_tasks.merge.post(self.mr._id)
+ assert_equal(self.mr.merge_task_status(), 'ready')
+ M.MonQTask.run_ready()
+ assert_equal(self.mr.merge_task_status(), 'complete')
http://git-wip-us.apache.org/repos/asf/allura/blob/e37f9437/Allura/allura/tests/test_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 13d5c17..d822229 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -64,6 +64,17 @@ class TestRepoTasks(unittest.TestCase):
assert_equal(post_event.call_args[0][2], None)
# ignore args[3] which is a traceback string
+ @mock.patch('allura.tasks.repo_tasks.session', autospec=True)
+ @mock.patch.object(M, 'MergeRequest', autospec=True)
+ def test_merge(self, MR, session):
+ mr = mock.Mock(_id='_id')
+ MR.query.get.return_value = mr
+ repo_tasks.merge(mr._id)
+ mr.app.repo.merge.assert_called_once_with(mr)
+ assert_equal(mr.status, 'merged')
+ session.assert_called_once_with(mr)
+ session.return_value.flush.assert_called_once_with(mr)
+
class TestEventTasks(unittest.TestCase):
http://git-wip-us.apache.org/repos/asf/allura/blob/e37f9437/ForgeGit/forgegit/tests/model/test_repository.py
----------------------------------------------------------------------
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index 231e479..d79fc7b 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -592,6 +592,52 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
}
assert_equals(payload, expected_payload)
+ def test_can_merge(self):
+ mr = mock.Mock(downstream_repo_url='downstream-url',
+ source_branch='source-branch',
+ target_branch='target-branch',
+ downstream=mock.Mock(commit_id='cid'))
+ git = mock.Mock()
+ git.merge_tree.return_value = 'clean merge'
+ self.repo._impl._git.git = git
+ assert_equal(self.repo.can_merge(mr), True)
+ git.fetch.assert_called_once_with('downstream-url', 'source-branch')
+ git.merge_base.assert_called_once_with('cid', 'target-branch')
+ git.merge_tree.assert_called_once_with(
+ git.merge_base.return_value,
+ 'target-branch',
+ 'cid')
+ git.merge_tree.return_value = '+<<<<<<<'
+ assert_equal(self.repo.can_merge(mr), False)
+
+ @mock.patch('forgegit.model.git_repo.tempfile', autospec=True)
+ @mock.patch('forgegit.model.git_repo.git', autospec=True)
+ @mock.patch('forgegit.model.git_repo.GitImplementation', autospec=True)
+ @mock.patch('forgegit.model.git_repo.shutil', autospec=True)
+ def test_merge(self, shutil, GitImplementation, git, tempfile):
+ mr = mock.Mock(downstream_repo_url='downstream-url',
+ source_branch='source-branch',
+ target_branch='target-branch',
+ downstream=mock.Mock(commit_id='cid'))
+ _git = mock.Mock()
+ self.repo._impl._git.git = _git
+ self.repo.merge(mr)
+ git.Repo.clone_from.assert_called_once_with(
+ self.repo.clone_url('rw'),
+ to_path=tempfile.mkdtemp.return_value,
+ bare=False)
+ tmp_repo = GitImplementation.return_value._git
+ assert_equal(
+ tmp_repo.git.fetch.call_args_list,
+ [mock.call('origin', 'target-branch'),
+ mock.call('downstream-url', 'source-branch')])
+ tmp_repo.git.checkout.assert_called_once_with('target-branch')
+ tmp_repo.git.merge.assert_called_once_with('cid')
+ tmp_repo.git.push.assert_called_once_with('origin', 'target-branch')
+ shutil.rmtree.assert_called_once_with(
+ tempfile.mkdtemp.return_value,
+ ignore_errors=True)
+
class TestGitImplementation(unittest.TestCase):