diff --git a/docs/references.rst b/docs/references.rst index 4d9a16b..db97ff8 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -17,16 +17,18 @@ Example:: The Reference type ==================== +.. autoclass:: pygit2.Reference + .. autoattribute:: pygit2.Reference.name .. autoattribute:: pygit2.Reference.shorthand .. autoattribute:: pygit2.Reference.target .. autoattribute:: pygit2.Reference.type +.. automethod:: pygit2.Reference.set_target .. automethod:: pygit2.Reference.delete .. automethod:: pygit2.Reference.rename .. automethod:: pygit2.Reference.resolve .. automethod:: pygit2.Reference.log -.. automethod:: pygit2.Reference.log_append Example:: diff --git a/pygit2/decl.h b/pygit2/decl.h index 510975b..c7b7001 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -30,6 +30,13 @@ typedef struct git_strarray { typedef int64_t git_off_t; typedef int64_t git_time_t; +typedef enum { + GIT_REF_INVALID = 0, + GIT_REF_OID = 1, + GIT_REF_SYMBOLIC = 2, + GIT_REF_LISTALL = 3, +} git_ref_t; + typedef enum { GIT_OK = 0, GIT_ERROR = -1, @@ -462,6 +469,9 @@ int git_repository_init_ext( const char *repo_path, git_repository_init_options *opts); +int git_repository_set_head(git_repository *repo, const char *refname, const git_signature *signature, const char *log_message); +int git_repository_set_head_detached(git_repository *repo, const git_oid *commitish, const git_signature *signature, const char *log_message); + /* * git_index */ diff --git a/pygit2/repository.py b/pygit2/repository.py index fd50d74..27db17a 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -275,7 +275,57 @@ class Repository(_Repository): oid = reference.resolve().target treeish = self[oid] self.checkout_tree(treeish, **kwargs) - self.head = refname + head = self.lookup_reference('HEAD') + if head.type == C.GIT_REF_SYMBOLIC: + from_ = self.head.shorthand + else: + from_ = head.target.hex + + try: + signature = self.default_signature + except: + signature = None + + reflog_text = "checkout: moving from %s to %s" % (from_, reference) + self.set_head(refname, signature, reflog_text) + + # + # Setting HEAD + # + def set_head(self, target, signature=None, message=None): + """Set HEAD to point to the given target + + Arguments: + + target + The new target for HEAD. Can be a string or Oid (to detach) + + signature + Signature to use for the reflog. If not provided, the repository's + default will be used + + message + Message to use for the reflog + """ + + sig_ptr = ffi.new('git_signature **') + if signature: + ffi.buffer(sig_ptr)[:] = signature._pointer[:] + + message_ptr = ffi.NULL + if message_ptr: + message_ptr = to_bytes(message) + + if isinstance(target, Oid): + oid = ffi.new('git_oid *') + ffi.buffer(oid)[:] = target.raw[:] + err = C.git_repository_set_head_detached(self._repo, oid, sig_ptr[0], message_ptr) + check_error(err) + return + + # if it's a string, then it's a reference name + err = C.git_repository_set_head(self._repo, to_bytes(target), sig_ptr[0], message_ptr) + check_error(err) # # Diff diff --git a/src/reference.c b/src/reference.c index cbd96d7..b89f023 100644 --- a/src/reference.c +++ b/src/reference.c @@ -205,10 +205,7 @@ Reference_resolve(Reference *self, PyObject *args) PyDoc_STRVAR(Reference_target__doc__, "The reference target: If direct the value will be an Oid object, if it\n" "is symbolic it will be an string with the full name of the target\n" - "reference.\n" - "\n" - "The target is writable. Setting the Reference's target to another Oid\n" - "object will direct the reference to that Oid instead."); + "reference.\n"); PyObject * Reference_target__get__(Reference *self) @@ -230,48 +227,80 @@ Reference_target__get__(Reference *self) return to_path(c_name); } -int -Reference_target__set__(Reference *self, PyObject *py_target) +PyDoc_STRVAR(Reference_set_target__doc__, + "set_target(target, [signature, message])\n" + "\n" + "Set the target of this reference.\n" + "\n" + "Update the reference using the given signature and message.\n" + "These will be used to fill the reflog entry which will be created\n" + "as a result of this update\n" + "\n" + "Arguments:\n" + "\n" + "target\n" + " The new target for this reference\n" + "signature\n" + " The signature to use for the reflog. If left out, the repository's\n" + " default identity will be used.\n" + "message\n" + " Message to use for the reflog.\n"); + +PyObject * +Reference_set_target(Reference *self, PyObject *args, PyObject *kwds) { git_oid oid; char *c_name; int err; git_reference *new_ref; + const git_signature *sig = NULL; + PyObject *py_target = NULL; + Signature *py_signature = NULL; + const char *message = NULL; + char *keywords[] = {"target", "signature", "message", NULL}; - CHECK_REFERENCE_INT(self); + CHECK_REFERENCE(self); + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O!s", keywords, + &py_target, &SignatureType, &py_signature, &message)) + return NULL; + + if (py_signature) + sig = py_signature->signature; /* Case 1: Direct */ if (GIT_REF_OID == git_reference_type(self->reference)) { err = py_oid_to_git_oid_expand(self->repo->repo, py_target, &oid); if (err < 0) - return err; + goto error; - err = git_reference_set_target(&new_ref, self->reference, &oid, NULL, NULL); + err = git_reference_set_target(&new_ref, self->reference, &oid, sig, message); if (err < 0) goto error; git_reference_free(self->reference); self->reference = new_ref; - return 0; + Py_RETURN_NONE; } /* Case 2: Symbolic */ c_name = py_path_to_c_str(py_target); if (c_name == NULL) - return -1; + return NULL; - err = git_reference_symbolic_set_target(&new_ref, self->reference, c_name, NULL, NULL); + err = git_reference_symbolic_set_target(&new_ref, self->reference, c_name, sig, message); free(c_name); if (err < 0) goto error; git_reference_free(self->reference); self->reference = new_ref; - return 0; + + Py_RETURN_NONE; error: Error_set(err); - return -1; + return NULL; } @@ -335,72 +364,6 @@ Reference_log(Reference *self) return (PyObject*)iter; } -PyDoc_STRVAR(Reference_log_append__doc__, - "log_append(oid, committer, message[, encoding])\n" - "\n" - "Append a reflog entry to the reference. If the oid is None then keep\n" - "the current reference's oid. The message parameter may be None."); - -PyObject * -Reference_log_append(Reference *self, PyObject *args) -{ - git_signature *committer; - const char *message = NULL; - git_reflog *reflog; - git_oid oid; - const git_oid *ref_oid; - int err; - PyObject *py_oid = NULL; - Signature *py_committer; - PyObject *py_message = NULL; - char *encoding = NULL; - git_repository *repo; - - CHECK_REFERENCE(self); - - /* Input parameters */ - if (!PyArg_ParseTuple(args, "OO!O|s", &py_oid, - &SignatureType, &py_committer, - &py_message, &encoding)) - return NULL; - - if (py_oid == Py_None) - ref_oid = git_reference_target(self->reference); - else { - err = py_oid_to_git_oid_expand(self->repo->repo, py_oid, &oid); - if (err < 0) - return NULL; - ref_oid = &oid; - } - - if (py_message != Py_None) { - message = py_str_to_c_str(py_message, encoding); - if (message == NULL) - return NULL; - } - - /* Go */ - repo = git_reference_owner(self->reference); - err = git_reflog_read(&reflog, repo, git_reference_name(self->reference)); - if (err < 0) { - free((void *)message); - return NULL; - } - - committer = (git_signature *)py_committer->signature; - err = git_reflog_append(reflog, ref_oid, committer, message); - if (!err) - err = git_reflog_write(reflog); - - git_reflog_free(reflog); - free((void *)message); - - if (err < 0) - return NULL; - - Py_RETURN_NONE; -} - PyDoc_STRVAR(Reference_get_object__doc__, "get_object() -> object\n" "\n" @@ -514,15 +477,15 @@ PyMethodDef Reference_methods[] = { METHOD(Reference, rename, METH_O), METHOD(Reference, resolve, METH_NOARGS), METHOD(Reference, log, METH_NOARGS), - METHOD(Reference, log_append, METH_VARARGS), METHOD(Reference, get_object, METH_NOARGS), + METHOD(Reference, set_target, METH_VARARGS | METH_KEYWORDS), {NULL} }; PyGetSetDef Reference_getseters[] = { GETTER(Reference, name), GETTER(Reference, shorthand), - GETSET(Reference, target), + GETTER(Reference, target), GETTER(Reference, type), {NULL} }; diff --git a/src/repository.c b/src/repository.c index c02a0f1..31075c2 100644 --- a/src/repository.c +++ b/src/repository.c @@ -177,38 +177,6 @@ Repository_head__get__(Repository *self) return wrap_reference(head, self); } -int -Repository_head__set__(Repository *self, PyObject *py_val) -{ - int err; - if (PyObject_TypeCheck(py_val, &OidType)) { - git_oid oid; - py_oid_to_git_oid(py_val, &oid); - err = git_repository_set_head_detached(self->repo, &oid, NULL, NULL); - if (err < 0) { - Error_set(err); - return -1; - } - } else { - const char *refname; - PyObject *trefname; - - refname = py_str_borrow_c_str(&trefname, py_val, NULL); - if (refname == NULL) - return -1; - - err = git_repository_set_head(self->repo, refname, NULL, NULL); - Py_DECREF(trefname); - if (err < 0) { - Error_set_str(err, refname); - return -1; - } - } - - return 0; -} - - PyDoc_STRVAR(Repository_head_is_detached__doc__, "A repository's HEAD is detached when it points directly to a commit\n" "instead of a branch."); @@ -1447,7 +1415,7 @@ PyMethodDef Repository_methods[] = { PyGetSetDef Repository_getseters[] = { GETTER(Repository, path), - GETSET(Repository, head), + GETTER(Repository, head), GETTER(Repository, head_is_detached), GETTER(Repository, head_is_unborn), GETTER(Repository, is_empty), diff --git a/test/test_reflog.py b/test/test_reflog.py deleted file mode 100644 index 1251c2b..0000000 --- a/test/test_reflog.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: UTF-8 -*- -# -# Copyright 2010-2014 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. - -"""Tests for reference log.""" - -from __future__ import absolute_import -from __future__ import unicode_literals - -from pygit2 import Signature -from . import utils - - -class ReflogTest(utils.RepoTestCase): - - def test_log_append(self): - repo = self.repo - master = repo.lookup_reference("refs/heads/master") - signature = Signature('xtao', 'xutao@douban.com') - master.log_append(None, signature, 'reflog') - self.assertTrue('reflog' in [entry.message for entry in master.log()]) diff --git a/test/test_refs.py b/test/test_refs.py index 0757b7a..35aea55 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -31,7 +31,7 @@ from __future__ import absolute_import from __future__ import unicode_literals import unittest -from pygit2 import GitError, GIT_REF_OID, GIT_REF_SYMBOLIC +from pygit2 import GitError, GIT_REF_OID, GIT_REF_SYMBOLIC, Signature from . import utils @@ -77,13 +77,13 @@ class ReferencesTest(utils.RepoTestCase): def test_reference_set_sha(self): NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = self.repo.lookup_reference('refs/heads/master') - reference.target = NEW_COMMIT + reference.set_target(NEW_COMMIT) self.assertEqual(reference.target.hex, NEW_COMMIT) def test_reference_set_sha_prefix(self): NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = self.repo.lookup_reference('refs/heads/master') - reference.target = NEW_COMMIT[0:6] + reference.set_target(NEW_COMMIT[0:6]) self.assertEqual(reference.target.hex, NEW_COMMIT) @@ -100,7 +100,7 @@ class ReferencesTest(utils.RepoTestCase): def test_set_target(self): reference = self.repo.lookup_reference('HEAD') self.assertEqual(reference.target, 'refs/heads/master') - reference.target = 'refs/heads/i18n' + reference.set_target('refs/heads/i18n') self.assertEqual(reference.target, 'refs/heads/i18n') def test_get_shorthand(self): @@ -109,6 +109,16 @@ class ReferencesTest(utils.RepoTestCase): reference = self.repo.create_reference('refs/remotes/origin/master', LAST_COMMIT) self.assertEqual(reference.shorthand, 'origin/master') + def test_set_target_with_message(self): + reference = self.repo.lookup_reference('HEAD') + self.assertEqual(reference.target, 'refs/heads/master') + sig = Signature('foo', 'bar') + msg = 'Hello log' + reference.set_target('refs/heads/i18n', signature=sig, message=msg) + self.assertEqual(reference.target, 'refs/heads/i18n') + self.assertEqual(list(reference.log())[0].message, msg) + self.assertEqualSignature(list(reference.log())[0].committer, sig) + def test_delete(self): repo = self.repo @@ -124,8 +134,6 @@ class ReferencesTest(utils.RepoTestCase): self.assertRaises(GitError, getattr, reference, 'name') self.assertRaises(GitError, getattr, reference, 'type') self.assertRaises(GitError, getattr, reference, 'target') - self.assertRaises(GitError, setattr, reference, 'target', LAST_COMMIT) - self.assertRaises(GitError, setattr, reference, 'target', "a/b/c") self.assertRaises(GitError, reference.delete) self.assertRaises(GitError, reference.resolve) self.assertRaises(GitError, reference.rename, "refs/tags/version2") diff --git a/test/test_repository.py b/test/test_repository.py index 566715a..72a5e64 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -76,10 +76,10 @@ class RepositoryTest(utils.BareRepoTestCase): def test_set_head(self): # Test setting a detatched HEAD. - self.repo.head = Oid(hex=PARENT_SHA) + self.repo.set_head(Oid(hex=PARENT_SHA)) self.assertEqual(self.repo.head.target.hex, PARENT_SHA) # And test setting a normal HEAD. - self.repo.head = "refs/heads/master" + self.repo.set_head("refs/heads/master") self.assertEqual(self.repo.head.name, "refs/heads/master") self.assertEqual(self.repo.head.target.hex, HEAD_SHA)