diff options
author | Vicent Martà <vicent@github.com> | 2012-02-15 11:38:40 -0800 |
---|---|---|
committer | Vicent Martà <vicent@github.com> | 2012-02-15 11:38:40 -0800 |
commit | 6117895fef92ddd08c437ce5a7cdc4bf56754cc3 (patch) | |
tree | a38b27a3bab5c44daaab2c6b07438dc3e5cf67d9 | |
parent | 0c3bae6268c404fc3717cc90ba1bc5db91c8cbe6 (diff) | |
parent | bf477ed4a86d4183f7e38e4667a1f623270bf5d2 (diff) | |
download | libgit2-6117895fef92ddd08c437ce5a7cdc4bf56754cc3.tar.gz |
Merge pull request #558 from schu/notes-api
Notes API
-rw-r--r-- | include/git2.h | 2 | ||||
-rw-r--r-- | include/git2/commit.h | 3 | ||||
-rw-r--r-- | include/git2/notes.h | 97 | ||||
-rw-r--r-- | include/git2/types.h | 3 | ||||
-rw-r--r-- | src/commit.c | 10 | ||||
-rw-r--r-- | src/notes.c | 439 | ||||
-rw-r--r-- | src/notes.h | 28 | ||||
-rw-r--r-- | src/util.h | 9 | ||||
-rw-r--r-- | tests-clar/commit/commit.c | 44 | ||||
-rw-r--r-- | tests-clar/notes/notes.c | 49 |
10 files changed, 680 insertions, 4 deletions
diff --git a/include/git2.h b/include/git2.h index 3d7c4f626..5a55bb284 100644 --- a/include/git2.h +++ b/include/git2.h @@ -40,4 +40,6 @@ #include "git2/status.h" #include "git2/indexer.h" +#include "git2/notes.h" + #endif diff --git a/include/git2/commit.h b/include/git2/commit.h index 6d8cf53af..c274b6b95 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -191,7 +191,8 @@ GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned i * will be updated to point to this commit. If the reference * is not direct, it will be resolved to a direct reference. * Use "HEAD" to update the HEAD of the current branch and - * make it point to this commit + * make it point to this commit. If the reference doesn't + * exist yet, it will be created. * * @param author Signature representing the author and the authory * time of this commit diff --git a/include/git2/notes.h b/include/git2/notes.h new file mode 100644 index 000000000..1b5944f9d --- /dev/null +++ b/include/git2/notes.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_note_h__ +#define INCLUDE_git_note_h__ + +#include "oid.h" + +/** + * @file git2/notes.h + * @brief Git notes management routines + * @defgroup git_note Git notes management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Read the note for an object + * + * The note must be freed manually by the user. + * + * @param note the note; NULL in case of error + * @param repo the Git repository + * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits" + * @param oid OID of the object + * + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_note_read(git_note **note, git_repository *repo, + const char *notes_ref, const git_oid *oid); + +/** + * Get the note message + * + * @param note + * @return the note message + */ +GIT_EXTERN(const char *) git_note_message(git_note *note); + + +/** + * Get the note object OID + * + * @param note + * @return the note object OID + */ +GIT_EXTERN(const git_oid *) git_note_oid(git_note *note); + + +/** + * Add a note for an object + * + * @param oid pointer to store the OID (optional); NULL in case of error + * @param repo the Git repository + * @param author signature of the notes commit author + * @param committer signature of the notes commit committer + * @param notes_ref OID reference to update (optional); defaults to "refs/notes/commits" + * @param oid The OID of the object + * @param oid The note to add for object oid + * + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_note_create(git_oid *out, git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *oid, + const char *note); + + +/** + * Remove the note for an object + * + * @param repo the Git repository + * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits" + * @param author signature of the notes commit author + * @param committer signature of the notes commit committer + * @param oid the oid which note's to be removed + * + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_note_remove(git_repository *repo, const char *notes_ref, + git_signature *author, git_signature *committer, + const git_oid *oid); + +/** + * Free a git_note object + * + * @param note git_note object + */ +GIT_EXTERN(void) git_note_free(git_note *note); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/types.h b/include/git2/types.h index 669e4cc4e..ffada630a 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -131,6 +131,9 @@ typedef struct git_reflog_entry git_reflog_entry; /** Representation of a reference log */ typedef struct git_reflog git_reflog; +/** Representation of a git note */ +typedef struct git_note git_note; + /** Time in a signature */ typedef struct git_time { git_time_t time; /** time in seconds from epoch */ diff --git a/src/commit.c b/src/commit.c index 64db0a707..189d8fe9e 100644 --- a/src/commit.c +++ b/src/commit.c @@ -146,10 +146,14 @@ int git_commit_create( git_reference *target; error = git_reference_lookup(&head, repo, update_ref); - if (error < GIT_SUCCESS) + if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) return git__rethrow(error, "Failed to create commit"); - error = git_reference_resolve(&target, head); + if (error != GIT_ENOTFOUND) { + update_ref = git_reference_target(head); + error = git_reference_resolve(&target, head); + } + if (error < GIT_SUCCESS) { if (error != GIT_ENOTFOUND) { git_reference_free(head); @@ -162,7 +166,7 @@ int git_commit_create( * point to) or after an orphan checkout, so if the target * branch doesn't exist yet, create it and return. */ - error = git_reference_create_oid(&target, repo, git_reference_target(head), oid, 1); + error = git_reference_create_oid(&target, repo, update_ref, oid, 1); git_reference_free(head); if (error == GIT_SUCCESS) diff --git a/src/notes.c b/src/notes.c new file mode 100644 index 000000000..81fc00361 --- /dev/null +++ b/src/notes.c @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "notes.h" + +#include "git2.h" +#include "refs.h" + +static int find_subtree(git_tree **subtree, const git_oid *root, + git_repository *repo, const char *target, int *fanout) +{ + int error; + unsigned int i; + git_tree *tree; + const git_tree_entry *entry; + + *subtree = NULL; + + error = git_tree_lookup(&tree, repo, root); + if (error < GIT_SUCCESS) { + if (error == GIT_ENOTFOUND) + return error; /* notes tree doesn't exist yet */ + return git__rethrow(error, "Failed to open notes tree"); + } + + for (i=0; i<git_tree_entrycount(tree); i++) { + entry = git_tree_entry_byindex(tree, i); + + if (!git__ishex(git_tree_entry_name(entry))) + continue; + + /* + * A notes tree follows a strict byte-based progressive fanout + * (i.e. using 2/38, 2/2/36, etc. fanouts, not e.g. 4/36 fanout) + */ + + if (S_ISDIR(git_tree_entry_attributes(entry)) + && strlen(git_tree_entry_name(entry)) == 2 + && !strncmp(git_tree_entry_name(entry), target + *fanout, 2)) { + + /* found matching subtree - unpack and resume lookup */ + + git_oid subtree_sha; + git_oid_cpy(&subtree_sha, git_tree_entry_id(entry)); + git_tree_free(tree); + + *fanout += 2; + + return find_subtree(subtree, &subtree_sha, repo, + target, fanout); + } + } + + *subtree = tree; + return GIT_SUCCESS; +} + +static int find_blob(git_oid *blob, git_tree *tree, const char *target) +{ + unsigned int i; + const git_tree_entry *entry; + + for (i=0; i<git_tree_entrycount(tree); i++) { + entry = git_tree_entry_byindex(tree, i); + + if (!strcmp(git_tree_entry_name(entry), target)) { + /* found matching note object - return */ + + git_oid_cpy(blob, git_tree_entry_id(entry)); + return GIT_SUCCESS; + } + } + return GIT_ENOTFOUND; +} + +static int note_write(git_oid *out, git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const char *note, + const git_oid *tree_sha, const char *target, + int nparents, git_commit **parents) +{ + int error, fanout = 0; + git_oid oid; + git_tree *tree = NULL; + git_tree_entry *entry; + git_treebuilder *tb; + + /* check for existing notes tree */ + + if (tree_sha) { + error = find_subtree(&tree, tree_sha, repo, target, &fanout); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to lookup subtree"); + + error = find_blob(&oid, tree, target + fanout); + if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) { + git_tree_free(tree); + return git__throw(GIT_ENOTFOUND, "Failed to read subtree %s", target); + } + + if (error == GIT_SUCCESS) { + git_tree_free(tree); + return git__throw(GIT_EEXISTS, "Note for `%s` exists already", target); + } + } + + /* no matching tree entry - add note object to target tree */ + + error = git_treebuilder_create(&tb, tree); + git_tree_free(tree); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to create treebuilder"); + + if (!tree_sha) + /* no notes tree yet - create fanout */ + fanout += 2; + + /* create note object */ + error = git_blob_create_frombuffer(&oid, repo, note, strlen(note)); + if (error < GIT_SUCCESS) { + git_treebuilder_free(tb); + return git__rethrow(error, "Failed to create note object"); + } + + error = git_treebuilder_insert(&entry, tb, target + fanout, &oid, 0100644); + if (error < GIT_SUCCESS) { + /* libgit2 doesn't support object removal (gc) yet */ + /* we leave an orphaned blob object behind - TODO */ + + git_treebuilder_free(tb); + return git__rethrow(error, "Failed to insert note object"); + } + + if (out) + git_oid_cpy(out, git_tree_entry_id(entry)); + + error = git_treebuilder_write(&oid, repo, tb); + git_treebuilder_free(tb); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to write notes tree"); + + if (!tree_sha) { + /* create fanout subtree */ + + char subtree[3]; + strncpy(subtree, target, 2); + subtree[2] = '\0'; + + error = git_treebuilder_create(&tb, NULL); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to create treebuilder"); + + error = git_treebuilder_insert(NULL, tb, subtree, &oid, 0040000); + if (error < GIT_SUCCESS) { + git_treebuilder_free(tb); + return git__rethrow(error, "Failed to insert note object"); + } + + error = git_treebuilder_write(&oid, repo, tb); + git_treebuilder_free(tb); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to write notes tree"); + } + + /* create new notes commit */ + + error = git_tree_lookup(&tree, repo, &oid); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to open new notes tree"); + + error = git_commit_create(&oid, repo, notes_ref, author, committer, + NULL, GIT_NOTES_DEFAULT_MSG_ADD, + tree, nparents, (const git_commit **) parents); + + git_tree_free(tree); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to create new notes commit"); + + return GIT_SUCCESS; +} + +static int note_lookup(git_note **out, git_repository *repo, + const git_oid *tree_sha, const char *target) +{ + int error, fanout = 0; + git_oid oid; + git_blob *blob; + git_tree *tree; + git_note *note; + + error = find_subtree(&tree, tree_sha, repo, target, &fanout); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to lookup subtree"); + + error = find_blob(&oid, tree, target + fanout); + if (error < GIT_SUCCESS) { + git_tree_free(tree); + return git__throw(GIT_ENOTFOUND, "No note found for object %s", + target); + } + git_tree_free(tree); + + error = git_blob_lookup(&blob, repo, &oid); + if (error < GIT_SUCCESS) + return git__throw(GIT_ERROR, "Failed to lookup note object"); + + note = git__malloc(sizeof(git_note)); + if (note == NULL) { + git_blob_free(blob); + return GIT_ENOMEM; + } + + git_oid_cpy(¬e->oid, &oid); + note->message = git__strdup(git_blob_rawcontent(blob)); + if (note->message == NULL) + error = GIT_ENOMEM; + + *out = note; + + git_blob_free(blob); + return error; +} + +static int note_remove(git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *tree_sha, + const char *target, int nparents, git_commit **parents) +{ + int error, fanout = 0; + git_oid oid; + git_tree *tree; + git_treebuilder *tb; + + error = find_subtree(&tree, tree_sha, repo, target, &fanout); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to lookup subtree"); + + error = find_blob(&oid, tree, target + fanout); + if (error < GIT_SUCCESS) { + git_tree_free(tree); + return git__throw(GIT_ENOTFOUND, "No note found for object %s", + target); + } + + error = git_treebuilder_create(&tb, tree); + git_tree_free(tree); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to create treebuilder"); + + error = git_treebuilder_remove(tb, target + fanout); + if (error < GIT_SUCCESS) { + git_treebuilder_free(tb); + return git__rethrow(error, "Failed to remove entry from notes tree"); + } + + error = git_treebuilder_write(&oid, repo, tb); + git_treebuilder_free(tb); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to write notes tree"); + + /* create new notes commit */ + + error = git_tree_lookup(&tree, repo, &oid); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to open new notes tree"); + + error = git_commit_create(&oid, repo, notes_ref, author, committer, + NULL, GIT_NOTES_DEFAULT_MSG_RM, + tree, nparents, (const git_commit **) parents); + + git_tree_free(tree); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to create new notes commit"); + + return error; +} + +int git_note_read(git_note **out, git_repository *repo, + const char *notes_ref, const git_oid *oid) +{ + int error; + char *target; + git_reference *ref; + git_commit *commit; + const git_oid *sha; + + *out = NULL; + + if (!notes_ref) + notes_ref = GIT_NOTES_DEFAULT_REF; + + error = git_reference_lookup(&ref, repo, notes_ref); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to lookup reference `%s`", notes_ref); + + assert(git_ref_type(ref) == GIT_REF_OID); + + sha = git_reference_oid(ref); + error = git_commit_lookup(&commit, repo, sha); + + git_reference_free(ref); + + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to find notes commit object"); + + sha = git_commit_tree_oid(commit); + git_commit_free(commit); + + target = git_oid_allocfmt(oid); + if (target == NULL) + return GIT_ENOMEM; + + error = note_lookup(out, repo, sha, target); + + git__free(target); + return error == GIT_SUCCESS ? GIT_SUCCESS : + git__rethrow(error, "Failed to read note"); +} + +int git_note_create(git_oid *out, git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *oid, + const char *note) +{ + int error, nparents = 0; + char *target; + git_oid sha; + git_commit *commit = NULL; + git_reference *ref; + + if (!notes_ref) + notes_ref = GIT_NOTES_DEFAULT_REF; + + error = git_reference_lookup(&ref, repo, notes_ref); + if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) + return git__rethrow(error, "Failed to lookup reference `%s`", notes_ref); + + if (error == GIT_SUCCESS) { + assert(git_ref_type(ref) == GIT_REF_OID); + + /* lookup existing notes tree oid */ + + git_oid_cpy(&sha, git_reference_oid(ref)); + git_reference_free(ref); + + error = git_commit_lookup(&commit, repo, &sha); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to find notes commit object"); + + git_oid_cpy(&sha, git_commit_tree_oid(commit)); + nparents++; + } + + target = git_oid_allocfmt(oid); + if (target == NULL) + return GIT_ENOMEM; + + error = note_write(out, repo, author, committer, notes_ref, + note, nparents ? &sha : NULL, target, + nparents, &commit); + + git__free(target); + git_commit_free(commit); + return error == GIT_SUCCESS ? GIT_SUCCESS : + git__rethrow(error, "Failed to write note"); +} + +int git_note_remove(git_repository *repo, const char *notes_ref, + git_signature *author, git_signature *committer, + const git_oid *oid) +{ + int error; + char *target; + git_oid sha; + git_commit *commit; + git_reference *ref; + + if (!notes_ref) + notes_ref = GIT_NOTES_DEFAULT_REF; + + error = git_reference_lookup(&ref, repo, notes_ref); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to lookup reference `%s`", notes_ref); + + assert(git_ref_type(ref) == GIT_REF_OID); + + git_oid_cpy(&sha, git_reference_oid(ref)); + git_reference_free(ref); + + error = git_commit_lookup(&commit, repo, &sha); + if (error < GIT_SUCCESS) + return git__rethrow(error, "Failed to find notes commit object"); + + git_oid_cpy(&sha, git_commit_tree_oid(commit)); + + target = git_oid_allocfmt(oid); + if (target == NULL) + return GIT_ENOMEM; + + error = note_remove(repo, author, committer, notes_ref, + &sha, target, 1, &commit); + + git__free(target); + git_commit_free(commit); + return error == GIT_SUCCESS ? GIT_SUCCESS : + git__rethrow(error, "Failed to read note"); +} + +const char * git_note_message(git_note *note) +{ + assert(note); + return note->message; +} + +const git_oid * git_note_oid(git_note *note) +{ + assert(note); + return ¬e->oid; +} + +void git_note_free(git_note *note) +{ + if (note == NULL) + return; + + git__free(note->message); + git__free(note); +} diff --git a/src/notes.h b/src/notes.h new file mode 100644 index 000000000..219db1ab0 --- /dev/null +++ b/src/notes.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_note_h__ +#define INCLUDE_note_h__ + +#include "common.h" + +#include "git2/oid.h" + +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" + +#define GIT_NOTES_DEFAULT_MSG_ADD \ + "Notes added by 'git_note_create' from libgit2" + +#define GIT_NOTES_DEFAULT_MSG_RM \ + "Notes removed by 'git_note_remove' from libgit2" + +struct git_note { + git_oid oid; + + char *message; +}; + +#endif /* INCLUDE_notes_h__ */ diff --git a/src/util.h b/src/util.h index 0eff90669..f77c91dfd 100644 --- a/src/util.h +++ b/src/util.h @@ -169,4 +169,13 @@ GIT_INLINE(int) git__fromhex(char h) return from_hex[(unsigned char) h]; } +GIT_INLINE(int) git__ishex(const char *str) +{ + unsigned i; + for (i=0; i<strlen(str); i++) + if (git__fromhex(str[i]) < 0) + return 0; + return 1; +} + #endif /* INCLUDE_util_h__ */ diff --git a/tests-clar/commit/commit.c b/tests-clar/commit/commit.c new file mode 100644 index 000000000..1205e5285 --- /dev/null +++ b/tests-clar/commit/commit.c @@ -0,0 +1,44 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; + +void test_commit_commit__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); +} + +void test_commit_commit__cleanup(void) +{ + git_repository_free(_repo); + cl_fixture_cleanup("testrepo.git"); +} + +void test_commit_commit__create_unexisting_update_ref(void) +{ + git_oid oid; + git_tree *tree; + git_commit *commit; + git_signature *s; + git_reference *ref; + + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + + git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, + NULL, "some msg", tree, 1, (const git_commit **) &commit)); + + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_assert(!git_oid_cmp(&oid, git_reference_oid(ref))); + + git_tree_free(tree); + git_commit_free(commit); + git_signature_free(s); + git_reference_free(ref); +} diff --git a/tests-clar/notes/notes.c b/tests-clar/notes/notes.c new file mode 100644 index 000000000..eeb25eca0 --- /dev/null +++ b/tests-clar/notes/notes.c @@ -0,0 +1,49 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_note *_note; +static git_blob *_blob; +static git_signature *_sig; + +void test_notes_notes__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); +} + +void test_notes_notes__cleanup(void) +{ + git_note_free(_note); + git_blob_free(_blob); + git_signature_free(_sig); + + git_repository_free(_repo); + cl_fixture_cleanup("testrepo.git"); +} + +void test_notes_notes__1(void) +{ + git_oid oid, note_oid; + + cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com")); + + cl_git_pass(git_note_create(¬e_oid, _repo, _sig, _sig, "refs/notes/some/namespace", &oid, "hello world\n")); + cl_git_pass(git_note_create(¬e_oid, _repo, _sig, _sig, NULL, &oid, "hello world\n")); + + cl_git_pass(git_note_read(&_note, _repo, NULL, &oid)); + + cl_assert(!strcmp(git_note_message(_note), "hello world\n")); + cl_assert(!git_oid_cmp(git_note_oid(_note), ¬e_oid)); + + cl_git_pass(git_blob_lookup(&_blob, _repo, ¬e_oid)); + cl_assert(!strcmp(git_note_message(_note), git_blob_rawcontent(_blob))); + + cl_git_fail(git_note_create(¬e_oid, _repo, _sig, _sig, NULL, &oid, "hello world\n")); + cl_git_fail(git_note_create(¬e_oid, _repo, _sig, _sig, "refs/notes/some/namespace", &oid, "hello world\n")); + + cl_git_pass(git_note_remove(_repo, NULL, _sig, _sig, &oid)); + cl_git_pass(git_note_remove(_repo, "refs/notes/some/namespace", _sig, _sig, &oid)); + + cl_git_fail(git_note_remove(_repo, NULL, _sig, _sig, ¬e_oid)); + cl_git_fail(git_note_remove(_repo, "refs/notes/some/namespace", _sig, _sig, &oid)); +} |