From 7e78f553e23db5e1526eaf4d526448d0104488f5 Mon Sep 17 00:00:00 2001 From: "Raphael Medaer (Escaux)" Date: Mon, 27 Feb 2017 15:28:37 +0100 Subject: [PATCH 1/9] Added methods to list and add worktrees. Basic Python binding to: - Add a new worktree (Repository#add_worktree(name, path) --> None) - List existing worktrees (Repository#list_worktrees() --> [string] --- src/repository.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/repository.c b/src/repository.c index 8646e899e..d390f96ed 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1793,6 +1793,68 @@ Repository_expand_id(Repository *self, PyObject *py_hex) return git_oid_to_python(&oid); } +PyDoc_STRVAR(Repository_add_worktree__doc__, + "add_worktree(name, path)\n" + "\n" + "Create a new worktree for this repository."); +PyObject * +Repository_add_worktree(Repository *self, PyObject *args) +{ + char *c_name; + char *c_path; + git_worktree *wt; + int err; + + if (!PyArg_ParseTuple(args, "ss", &c_name, &c_path)) + return NULL; + + err = git_worktree_add(&wt, self->repo, c_name, c_path); + if (err < 0) + return Error_set(err); + + git_worktree_free(wt); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Repository_list_worktrees__doc__, + "list_worktrees() -> [str, ...]\n" + "\n" + "Return a list with all the worktrees of this repository."); + +PyObject * +Repository_list_worktrees(Repository *self, PyObject *args) +{ + git_strarray c_result; + PyObject *py_result, *py_string; + unsigned index; + int err; + + /* Get the C result */ + err = git_worktree_list(&c_result, self->repo); + if (err < 0) + return Error_set(err); + + /* Create a new PyTuple */ + py_result = PyList_New(c_result.count); + if (py_result == NULL) + goto out; + + /* Fill it */ + for (index=0; index < c_result.count; index++) { + py_string = to_path(c_result.strings[index]); + if (py_string == NULL) { + Py_CLEAR(py_result); + goto out; + } + PyList_SET_ITEM(py_result, index, py_string); + } + +out: + git_strarray_free(&c_result); + return py_result; +} + PyMethodDef Repository_methods[] = { METHOD(Repository, create_blob, METH_VARARGS), METHOD(Repository, create_blob_fromworkdir, METH_VARARGS), @@ -1829,6 +1891,8 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, reset, METH_VARARGS), METHOD(Repository, free, METH_NOARGS), METHOD(Repository, expand_id, METH_O), + METHOD(Repository, add_worktree, METH_VARARGS), + METHOD(Repository, list_worktrees, METH_VARARGS), METHOD(Repository, _from_c, METH_VARARGS), METHOD(Repository, _disown, METH_NOARGS), {NULL} From 69720fc99348a95f3614cf53445f0033ffba5fc7 Mon Sep 17 00:00:00 2001 From: "Raphael Medaer (Escaux)" Date: Mon, 27 Feb 2017 17:06:47 +0100 Subject: [PATCH 2/9] Added Worktree PyObject. In this commit I added a new PyObject: Worktree. This is the binding of git_worktree from libgit2. Still have to implement prune, lock and unlock but you can at list get Worktree name and path. --- src/pygit2.c | 7 +++ src/repository.c | 5 +- src/types.h | 7 +++ src/worktree.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++ src/worktree.h | 47 +++++++++++++++ 5 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 src/worktree.c create mode 100644 src/worktree.h diff --git a/src/pygit2.c b/src/pygit2.c index 6ff7c4efa..f2f12cac9 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -67,6 +67,7 @@ extern PyTypeObject RemoteType; extern PyTypeObject RefspecType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; +extern PyTypeObject WorktreeType; PyDoc_STRVAR(discover_repository__doc__, @@ -297,6 +298,12 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_REF_SYMBOLIC) ADD_CONSTANT_INT(m, GIT_REF_LISTALL) + /* + * Worktree + */ + INIT_TYPE(WorktreeType, NULL, NULL) + ADD_TYPE(m, Worktree) + /* * Branches */ diff --git a/src/repository.c b/src/repository.c index d390f96ed..9867dd71e 100644 --- a/src/repository.c +++ b/src/repository.c @@ -38,6 +38,7 @@ #include "diff.h" #include "branch.h" #include "signature.h" +#include "worktree.h" #include #include @@ -1812,9 +1813,7 @@ Repository_add_worktree(Repository *self, PyObject *args) if (err < 0) return Error_set(err); - git_worktree_free(wt); - - Py_RETURN_NONE; + return wrap_worktree(self, wt); } PyDoc_STRVAR(Repository_list_worktrees__doc__, diff --git a/src/types.h b/src/types.h index 1dbf6b18f..0fbfe84b5 100644 --- a/src/types.h +++ b/src/types.h @@ -75,6 +75,13 @@ SIMPLE_TYPE(Tree, git_tree, tree) SIMPLE_TYPE(Blob, git_blob, blob) SIMPLE_TYPE(Tag, git_tag, tag) +/* git_worktree */ +typedef struct { + PyObject_HEAD + Repository *repo; + git_worktree *worktree; +} Worktree; + /* git_note */ typedef struct { PyObject_HEAD diff --git a/src/worktree.c b/src/worktree.c new file mode 100644 index 000000000..2079936b1 --- /dev/null +++ b/src/worktree.c @@ -0,0 +1,146 @@ +/* + * Copyright 2010-2015 The pygit2 contributors + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#define PY_SSIZE_T_CLEAN +#include +#include +#include "error.h" +#include "utils.h" +#include "types.h" +#include "worktree.h" + +PyDoc_STRVAR(Worktree_prune__doc__, + "Prune a worktree object."); + +PyObject * +Worktree_prune(Worktree *self, PyObject* args) +{ + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Worktree_name__doc__, + "Gets name worktree\n"); + +PyObject * +Worktree_name__get__(Worktree *self) +{ + return to_unicode(self->worktree->name, NULL, NULL); +} + +PyDoc_STRVAR(Worktree_path__doc__, + "Gets path worktree\n"); + +PyObject * +Worktree_path__get__(Worktree *self) +{ + return to_unicode(self->worktree->gitlink_path, NULL, NULL); + return NULL; +} + +static void +Worktree_dealloc(Worktree *self) +{ + Py_CLEAR(self->repo); + git_worktree_free(self->worktree); + PyObject_Del(self); +} + + +PyMethodDef Worktree_methods[] = { + METHOD(Worktree, prune, METH_VARARGS), + {NULL} +}; + +PyGetSetDef Worktree_getseters[] = { + GETTER(Worktree, path), + GETTER(Worktree, name), + {NULL} +}; + +PyDoc_STRVAR(Worktree__doc__, "Worktree object."); + +PyTypeObject WorktreeType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.Worktree", /* tp_name */ + sizeof(Worktree), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Worktree_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + Worktree__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Worktree_methods, /* tp_methods */ + 0, /* tp_members */ + Worktree_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +PyObject * +wrap_worktree(Repository* repo, git_worktree* wt) +{ + Worktree* py_wt = NULL; + + py_wt = PyObject_New(Worktree, &WorktreeType); + if (py_wt == NULL) { + PyErr_NoMemory(); + return NULL; + } + + py_wt->repo = repo; + Py_INCREF(repo); + py_wt->worktree = wt; + + return (PyObject*) py_wt; +} + + diff --git a/src/worktree.h b/src/worktree.h new file mode 100644 index 000000000..12bb44a89 --- /dev/null +++ b/src/worktree.h @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2015 The pygit2 contributors + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDE_pygit2_worktree_h +#define INCLUDE_pygit2_worktree_h + +#define PY_SSIZE_T_CLEAN +#include +#include +#include + +typedef struct git_worktree { + char *name; + char *gitlink_path; + char *gitdir_path; + char *commondir_path; + char *parent_path; + int locked:1; +} git_worktree; + +PyObject* wrap_worktree(Repository* repo, git_worktree* wt); + +#endif From fed0469606e49fedd8aaf848d050bc2da1e311a2 Mon Sep 17 00:00:00 2001 From: "Raphael Medaer (Escaux)" Date: Mon, 27 Feb 2017 17:30:59 +0100 Subject: [PATCH 3/9] Added method to lookup a worktree from its name Implemented Repository#lookup_worktree(name) -> Worktree --- src/repository.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/repository.c b/src/repository.c index 9867dd71e..ce046cd63 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1816,11 +1816,31 @@ Repository_add_worktree(Repository *self, PyObject *args) return wrap_worktree(self, wt); } -PyDoc_STRVAR(Repository_list_worktrees__doc__, - "list_worktrees() -> [str, ...]\n" - "\n" - "Return a list with all the worktrees of this repository."); +PyDoc_STRVAR(Repository_lookup_worktree__doc__, + "lookup_worktree(name) -> Worktree\n" + "\n" + "Lookup a worktree from its name."); +PyObject * +Repository_lookup_worktree(Repository *self, PyObject *args) +{ + char *c_name; + git_worktree *wt; + int err; + + if (!PyArg_ParseTuple(args, "s", &c_name)) + return NULL; + err = git_worktree_lookup(&wt, self->repo, c_name); + if (err < 0) + return Error_set(err); + + return wrap_worktree(self, wt); +} + +PyDoc_STRVAR(Repository_list_worktrees__doc__, + "list_worktrees() -> [str, ...]\n" + "\n" + "Return a list with all the worktrees of this repository."); PyObject * Repository_list_worktrees(Repository *self, PyObject *args) { @@ -1891,6 +1911,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, free, METH_NOARGS), METHOD(Repository, expand_id, METH_O), METHOD(Repository, add_worktree, METH_VARARGS), + METHOD(Repository, lookup_worktree, METH_VARARGS), METHOD(Repository, list_worktrees, METH_VARARGS), METHOD(Repository, _from_c, METH_VARARGS), METHOD(Repository, _disown, METH_NOARGS), From 6b36cfcbc7626482869962ec77f995324bc0fef5 Mon Sep 17 00:00:00 2001 From: "Raphael Medaer (Escaux)" Date: Mon, 27 Feb 2017 18:16:00 +0100 Subject: [PATCH 4/9] Implemented methods to prune a Worktree. Implemented method and getter: - Worktree#prune(force=False) - Worktree#is_prunable --- src/worktree.c | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/worktree.c b/src/worktree.c index 2079936b1..ca3009ec6 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -33,19 +33,9 @@ #include "types.h" #include "worktree.h" -PyDoc_STRVAR(Worktree_prune__doc__, - "Prune a worktree object."); - -PyObject * -Worktree_prune(Worktree *self, PyObject* args) -{ - Py_RETURN_NONE; -} - PyDoc_STRVAR(Worktree_name__doc__, - "Gets name worktree\n"); - + "Gets name worktree\n"); PyObject * Worktree_name__get__(Worktree *self) { @@ -53,13 +43,40 @@ Worktree_name__get__(Worktree *self) } PyDoc_STRVAR(Worktree_path__doc__, - "Gets path worktree\n"); - + "Gets path worktree\n"); PyObject * Worktree_path__get__(Worktree *self) { return to_unicode(self->worktree->gitlink_path, NULL, NULL); - return NULL; +} + +PyDoc_STRVAR(Worktree_is_prunable__doc__, + "Is the worktree prunable with the given set of flags?\n"); +PyObject * +Worktree_is_prunable__get__(Worktree *self, PyObject *args) +{ + return (git_worktree_is_prunable(self->worktree, 0) > 0) ? Py_True : Py_False; +} + +PyDoc_STRVAR(Worktree_prune__doc__, + "prune(force=False)\n" + "\n" + "Prune a worktree object."); +PyObject * +Worktree_prune(Worktree *self, PyObject *args) +{ + int err, force = 0; + + if (!PyArg_ParseTuple(args, "|i", &force)) + return NULL; + + err = git_worktree_prune(self->worktree, force & (GIT_WORKTREE_PRUNE_VALID | GIT_WORKTREE_PRUNE_LOCKED)); + if (err < 0) + return Error_set(err); + + // TODO should I have to deallocate myself ? + + Py_RETURN_NONE; } static void @@ -79,6 +96,7 @@ PyMethodDef Worktree_methods[] = { PyGetSetDef Worktree_getseters[] = { GETTER(Worktree, path), GETTER(Worktree, name), + GETTER(Worktree, is_prunable), {NULL} }; From b7f36c8fc48ae9787b148061bedf8310626af3da Mon Sep 17 00:00:00 2001 From: "Raphael Medaer (Escaux)" Date: Mon, 27 Feb 2017 18:26:25 +0100 Subject: [PATCH 5/9] Added getter for worktree Git directory --- src/worktree.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/worktree.c b/src/worktree.c index ca3009ec6..9448c85cc 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -50,6 +50,14 @@ Worktree_path__get__(Worktree *self) return to_unicode(self->worktree->gitlink_path, NULL, NULL); } +PyDoc_STRVAR(Worktree_git_path__doc__, + "Gets dir within .git path\n"); +PyObject * +Worktree_git_path__get__(Worktree *self) +{ + return to_unicode(self->worktree->gitdir_path, NULL, NULL); +} + PyDoc_STRVAR(Worktree_is_prunable__doc__, "Is the worktree prunable with the given set of flags?\n"); PyObject * @@ -95,6 +103,7 @@ PyMethodDef Worktree_methods[] = { PyGetSetDef Worktree_getseters[] = { GETTER(Worktree, path), + GETTER(Worktree, git_path), GETTER(Worktree, name), GETTER(Worktree, is_prunable), {NULL} From 0bc9f43fe365702d56fafce360e10effcddf8b49 Mon Sep 17 00:00:00 2001 From: Nick Hynes Date: Fri, 28 Jul 2017 09:15:36 -0400 Subject: [PATCH 6/9] Update worktree_{add, prune} to use opts --- src/repository.c | 3 ++- src/worktree.c | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/repository.c b/src/repository.c index ce046cd63..484772adb 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1805,11 +1805,12 @@ Repository_add_worktree(Repository *self, PyObject *args) char *c_path; git_worktree *wt; int err; + git_worktree_add_options add_opts = GIT_WORKTREE_ADD_OPTIONS_INIT; if (!PyArg_ParseTuple(args, "ss", &c_name, &c_path)) return NULL; - err = git_worktree_add(&wt, self->repo, c_name, c_path); + err = git_worktree_add(&wt, self->repo, c_name, c_path, &add_opts); if (err < 0) return Error_set(err); diff --git a/src/worktree.c b/src/worktree.c index 9448c85cc..c494a18c2 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -74,16 +74,18 @@ PyObject * Worktree_prune(Worktree *self, PyObject *args) { int err, force = 0; + git_worktree_prune_options prune_opts; if (!PyArg_ParseTuple(args, "|i", &force)) return NULL; - err = git_worktree_prune(self->worktree, force & (GIT_WORKTREE_PRUNE_VALID | GIT_WORKTREE_PRUNE_LOCKED)); + git_worktree_prune_init_options(&prune_opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION); + prune_opts.flags = force & (GIT_WORKTREE_PRUNE_VALID | GIT_WORKTREE_PRUNE_LOCKED); + + err = git_worktree_prune(self->worktree, &prune_opts); if (err < 0) return Error_set(err); - // TODO should I have to deallocate myself ? - Py_RETURN_NONE; } From 8590b54f63c81750c819a325b9fd7b3d45a40032 Mon Sep 17 00:00:00 2001 From: Nick Hynes Date: Fri, 28 Jul 2017 21:36:49 -0400 Subject: [PATCH 7/9] Add worktree methods to repository.h --- src/repository.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/repository.h b/src/repository.h index eb2d06717..57b70cc94 100644 --- a/src/repository.h +++ b/src/repository.h @@ -62,6 +62,9 @@ PyObject* Repository_listall_reference_objects(Repository *self, PyObject *args); PyObject* Repository_listall_branches(Repository *self, PyObject *args); PyObject* Repository_lookup_reference(Repository *self, PyObject *py_name); +PyObject* Repository_add_worktree(Repository *self, PyObject *args); +PyObject* Repository_lookup_worktree(Repository *self, PyObject *py_name); +PyObject* Repository_list_worktrees(Repository *self, PyObject *args); PyObject* Repository_create_reference(Repository *self, PyObject *args, PyObject* kw); From 5f3bf12860cd14fd34fd0371484dc3303e82ae6e Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Sun, 18 Mar 2018 14:21:15 -0500 Subject: [PATCH 8/9] Add unit test for worktree support --- test/test_repository.py | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/test_repository.py b/test/test_repository.py index d2eaa1524..03f870b8a 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -34,6 +34,7 @@ # Import from the Standard Library import binascii import unittest +import shutil import tempfile import os from os.path import join, realpath @@ -632,5 +633,57 @@ def test_clone_with_checkout_branch(self): # # not sure how to test this either... couldn't find pushspec # # self.assertEqual(repo.remotes[0].fetchspec, "refs/heads/test") +class WorktreeTestCase(utils.RepoTestCase): + + def test_worktree(self): + worktree_name = 'foo' + worktree_dir = tempfile.mkdtemp() + # Delete temp path so that it's not present when we attempt to add the + # worktree later + os.rmdir(worktree_dir) + + def _check_worktree(worktree): + path = os.path.join(worktree_dir, '.git') + git_path = os.path.join(self.repo.path, 'worktrees', worktree_name) + + # Confirm the name attribute matches the specified name + self.assertEqual(worktree.name, worktree_name) + # Confirm the path attribute points to the correct path + self.assertEqual(worktree.path.rstrip(os.sep), path) + # The "gitdir" in a worktree should be a file with a reference to + # the actual gitdir. Let's make sure that the path exists and is a + # file. + self.assertTrue(os.path.isfile(path)) + # Confirm the git_path attribute points to the correct path + self.assertEqual(worktree.git_path.rstrip(os.sep), git_path) + # Confirm the worktree directory in the main checkout's gitdir + # actually exists + self.assertTrue(os.path.isdir(git_path)) + + # We should have zero worktrees + self.assertEqual(self.repo.list_worktrees(), []) + # Add a worktree + worktree = self.repo.add_worktree(worktree_name, worktree_dir) + # Check that the worktree was added properly + _check_worktree(worktree) + # We should have one worktree now + self.assertEqual(self.repo.list_worktrees(), [worktree_name]) + # Test that lookup_worktree() returns a properly-instantiated + # pygit2._Worktree object + _check_worktree(self.repo.lookup_worktree(worktree_name)) + # Remove the worktree dir + shutil.rmtree(worktree_dir) + # Prune the worktree. For some reason, libgit2 treats a worktree as + # valid unless both the worktree directory and data dir under + # $gitdir/worktrees are gone. This doesn't make much sense since the + # normal usage involves removing the worktree directory and then + # pruning. So, for now we have to force the prune. This may be + # something to take up with libgit2. + worktree.prune(True) + self.assertEqual(self.repo.list_worktrees(), []) + # Confirm that the repo's data dir has been removed + self.assertFalse(os.path.isdir(worktree.git_path)) + + if __name__ == '__main__': unittest.main() From a4870448a8234ae9d450e8edca74a82e10a05084 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Sun, 18 Mar 2018 15:10:42 -0500 Subject: [PATCH 9/9] Normalize windows paths using os.path.realpath() pygit2 produces paths with forward slashes, while the paths being compared in the test use escaped backslashes. Using realpath on both the path emitted by pygit2 and the expected path ensures this difference does not interfere with comparisons in the test. --- test/test_repository.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/test_repository.py b/test/test_repository.py index 03f870b8a..b49f92744 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -643,19 +643,21 @@ def test_worktree(self): os.rmdir(worktree_dir) def _check_worktree(worktree): - path = os.path.join(worktree_dir, '.git') - git_path = os.path.join(self.repo.path, 'worktrees', worktree_name) + path = os.path.realpath( + os.path.join(worktree_dir, '.git')) + git_path = os.path.realpath( + os.path.join(self.repo.path, 'worktrees', worktree_name)) # Confirm the name attribute matches the specified name self.assertEqual(worktree.name, worktree_name) # Confirm the path attribute points to the correct path - self.assertEqual(worktree.path.rstrip(os.sep), path) + self.assertEqual(os.path.realpath(worktree.path), path) # The "gitdir" in a worktree should be a file with a reference to # the actual gitdir. Let's make sure that the path exists and is a # file. self.assertTrue(os.path.isfile(path)) # Confirm the git_path attribute points to the correct path - self.assertEqual(worktree.git_path.rstrip(os.sep), git_path) + self.assertEqual(os.path.realpath(worktree.git_path), git_path) # Confirm the worktree directory in the main checkout's gitdir # actually exists self.assertTrue(os.path.isdir(git_path)) @@ -675,7 +677,7 @@ def _check_worktree(worktree): shutil.rmtree(worktree_dir) # Prune the worktree. For some reason, libgit2 treats a worktree as # valid unless both the worktree directory and data dir under - # $gitdir/worktrees are gone. This doesn't make much sense since the + # $GIT_DIR/worktrees are gone. This doesn't make much sense since the # normal usage involves removing the worktree directory and then # pruning. So, for now we have to force the prune. This may be # something to take up with libgit2.