static int write_back(git_object *object) { int error; git_oid new_id; assert(object); assert(object->source.open); assert(object->modified); object->source.raw.len = object->source.written_bytes; if ((error = git_odb_write(&new_id, object->repo->db, &object->source.raw)) < GIT_SUCCESS) return error; if (!object->in_memory) git_hashtable_remove(object->repo->objects, &object->id); git_oid_cpy(&object->id, &new_id); git_hashtable_insert(object->repo->objects, &object->id, object); object->source.write_ptr = NULL; object->source.written_bytes = 0; object->modified = 0; object->in_memory = 0; git_object__source_close(object); return GIT_SUCCESS; }
static int write_tag_annotation( git_oid *oid, git_repository *repo, const char *tag_name, const git_object *target, const git_signature *tagger, const char *message) { git_buf tag = GIT_BUF_INIT; git_odb *odb; git_oid__writebuf(&tag, "object ", git_object_id(target)); git_buf_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); git_buf_printf(&tag, "tag %s\n", tag_name); git_signature__writebuf(&tag, "tagger ", tagger); git_buf_putc(&tag, '\n'); if (git_buf_puts(&tag, message) < 0) goto on_error; if (git_repository_odb__weakptr(&odb, repo) < 0) goto on_error; if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0) goto on_error; git_buf_free(&tag); return 0; on_error: git_buf_free(&tag); giterr_set(GITERR_OBJECT, "Failed to create tag annotation."); return -1; }
static int write_file_filtered( git_oid *oid, git_odb *odb, const char *full_path, git_vector *filters) { int error; git_buf source = GIT_BUF_INIT; git_buf dest = GIT_BUF_INIT; if ((error = git_futils_readbuffer(&source, full_path)) < 0) return error; error = git_filters_apply(&dest, &source, filters); /* Free the source as soon as possible. This can be big in memory, * and we don't want to ODB write to choke */ git_buf_free(&source); /* Write the file to disk if it was properly filtered */ if (!error) error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB); git_buf_free(&dest); return error; }
int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) { unsigned int i; int error; git_buf tree = GIT_BUF_INIT; assert(bld); sort_entries(bld); /* Grow the buffer beforehand to an estimated size */ git_buf_grow(&tree, bld->entries.length * 72); for (i = 0; i < bld->entries.length; ++i) { git_tree_entry *entry = bld->entries.contents[i]; if (entry->removed) continue; git_buf_printf(&tree, "%o ", entry->attr); git_buf_put(&tree, entry->filename, entry->filename_len + 1); git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ); } if (git_buf_oom(&tree)) { git_buf_free(&tree); return git__throw(GIT_ENOMEM, "Not enough memory to build the tree data"); } error = git_odb_write(oid, git_repository_database(repo), tree.ptr, tree.size, GIT_OBJ_TREE); git_buf_free(&tree); return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write tree"); }
void test_pack_indexer__corrupt_length(void) { git_indexer *idx = NULL; git_transfer_progress stats = { 0 }; git_repository *repo; git_odb *odb; git_oid id, should_id; cl_git_pass(git_repository_init(&repo, "thin.git", true)); cl_git_pass(git_repository_odb(&odb, repo)); /* Store the missing base into your ODB so the indexer can fix the pack */ cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJ_BLOB)); git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); cl_assert_equal_oid(&should_id, &id); cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL, NULL)); cl_git_pass(git_indexer_append( idx, corrupt_thin_pack, corrupt_thin_pack_len, &stats)); cl_git_fail(git_indexer_commit(idx, &stats)); cl_assert(giterr_last() != NULL); cl_assert_equal_i(giterr_last()->klass, GITERR_ZLIB); git_indexer_free(idx); git_odb_free(odb); git_repository_free(repo); }
void test_write_object_permission( mode_t dir_mode, mode_t file_mode, mode_t expected_dir_mode, mode_t expected_file_mode) { git_odb *odb; git_odb_backend *backend; git_oid oid; struct stat statbuf; mode_t mask, os_mask; /* Windows does not return group/user bits from stat, * files are never executable. */ #ifdef GIT_WIN32 os_mask = 0600; #else os_mask = 0777; #endif mask = p_umask(0); p_umask(mask); cl_git_pass(git_odb_new(&odb)); cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, 0, dir_mode, file_mode)); cl_git_pass(git_odb_add_backend(odb, backend, 1)); cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJECT_BLOB)); cl_git_pass(p_stat("test-objects/67", &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_dir_mode & ~mask) & os_mask); cl_git_pass(p_stat("test-objects/67/b808feb36201507a77f85e6d898f0a2836e4a5", &statbuf)); cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_file_mode & ~mask) & os_mask); git_odb_free(odb); }
void test_odb_freshen__loose_object(void) { git_oid expected_id, id; struct stat before, after; struct p_timeval old_times[2]; cl_git_pass(git_oid_fromstr(&expected_id, LOOSE_ID)); old_times[0].tv_sec = 1234567890; old_times[0].tv_usec = 0; old_times[1].tv_sec = 1234567890; old_times[1].tv_usec = 0; /* set time to way back */ cl_must_pass(p_utimes("testrepo.git/objects/" LOOSE_FN, old_times)); cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_FN, &before)); cl_git_pass(git_odb_write(&id, odb, LOOSE_STR, CONST_STRLEN(LOOSE_STR), GIT_OBJ_BLOB)); cl_assert_equal_oid(&expected_id, &id); cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_FN, &after)); cl_assert(before.st_atime < after.st_atime); cl_assert(before.st_mtime < after.st_mtime); }
static int git_commit__create_internal( git_oid *id, git_repository *repo, const char *update_ref, const git_signature *author, const git_signature *committer, const char *message_encoding, const char *message, const git_oid *tree, git_commit_parent_callback parent_cb, void *parent_payload, bool validate) { int error; git_odb *odb; git_reference *ref = NULL; git_buf buf = GIT_BUF_INIT; const git_oid *current_id = NULL; git_array_oid_t parents = GIT_ARRAY_INIT; if (update_ref) { error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); if (error < 0 && error != GIT_ENOTFOUND) return error; } giterr_clear(); if (ref) current_id = git_reference_target(ref); if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0) goto cleanup; error = git_commit__create_buffer_internal(&buf, author, committer, message_encoding, message, tree, &parents); if (error < 0) goto cleanup; if (git_repository_odb__weakptr(&odb, repo) < 0) goto cleanup; if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJ_COMMIT) < 0) goto cleanup; if (update_ref != NULL) { error = git_reference__update_for_commit( repo, ref, update_ref, id, "commit"); goto cleanup; } cleanup: git_array_clear(parents); git_reference_free(ref); git_buf_free(&buf); return error; }
void test_index_collision__initialize(void) { g_repo = cl_git_sandbox_init("empty_standard_repo"); cl_git_pass(git_repository_odb(&g_odb, g_repo)); cl_git_pass(git_repository_index(&g_index, g_repo)); cl_git_pass(git_odb_write(&g_empty_id, g_odb, "", 0, GIT_OBJ_BLOB)); }
void test_odb_loose__fsync_obeys_repo_setting(void) { git_repository *repo; git_odb *odb; git_oid oid; cl_git_pass(git_repository_init(&repo, "test-objects", 1)); cl_git_pass(git_repository_odb__weakptr(&odb, repo)); cl_git_pass(git_odb_write(&oid, odb, "No fsync here\n", 14, GIT_OBJECT_BLOB)); cl_assert(p_fsync__cnt == 0); git_repository_free(repo); cl_git_pass(git_repository_open(&repo, "test-objects")); cl_repo_set_bool(repo, "core.fsyncObjectFiles", true); cl_git_pass(git_repository_odb__weakptr(&odb, repo)); cl_git_pass(git_odb_write(&oid, odb, "Now fsync\n", 10, GIT_OBJECT_BLOB)); cl_assert(p_fsync__cnt > 0); git_repository_free(repo); }
int git_commit_create( git_oid *oid, git_repository *repo, const char *update_ref, const git_signature *author, const git_signature *committer, const char *message_encoding, const char *message, const git_tree *tree, int parent_count, const git_commit *parents[]) { git_buf commit = GIT_BUF_INIT; int i; git_odb *odb; assert(git_object_owner((const git_object *)tree) == repo); git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree)); for (i = 0; i < parent_count; ++i) { assert(git_object_owner((const git_object *)parents[i]) == repo); git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i])); } git_signature__writebuf(&commit, "author ", author); git_signature__writebuf(&commit, "committer ", committer); if (message_encoding != NULL) git_buf_printf(&commit, "encoding %s\n", message_encoding); git_buf_putc(&commit, '\n'); if (git_buf_puts(&commit, message) < 0) goto on_error; if (git_repository_odb__weakptr(&odb, repo) < 0) goto on_error; if (git_odb_write(oid, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0) goto on_error; git_buf_free(&commit); if (update_ref != NULL) return git_reference__update(repo, oid, update_ref); return 0; on_error: git_buf_free(&commit); giterr_set(GITERR_OBJECT, "Failed to create commit."); return -1; }
static void write_object_to_loose_odb(int fsync) { git_odb *odb; git_odb_backend *backend; git_oid oid; cl_git_pass(git_odb_new(&odb)); cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, fsync, 0777, 0666)); cl_git_pass(git_odb_add_backend(odb, backend, 1)); cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJECT_BLOB)); git_odb_free(odb); }
void test_odb_freshen__packed_object(void) { git_oid expected_id, id; struct stat before, after; struct p_timeval old_times[2]; cl_git_pass(git_oid_fromstr(&expected_id, PACKED_ID)); old_times[0].tv_sec = 1234567890; old_times[0].tv_usec = 0; old_times[1].tv_sec = 1234567890; old_times[1].tv_usec = 0; /* set time to way back */ cl_must_pass(p_utimes("testrepo.git/objects/pack/" PACKED_FN, old_times)); cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &before)); /* ensure that packfile is freshened */ cl_git_pass(git_odb_write(&id, odb, PACKED_STR, CONST_STRLEN(PACKED_STR), GIT_OBJ_BLOB)); cl_assert_equal_oid(&expected_id, &id); cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); cl_assert(before.st_atime < after.st_atime); cl_assert(before.st_mtime < after.st_mtime); memcpy(&before, &after, sizeof(struct stat)); /* ensure that the pack file is not freshened again immediately */ cl_git_pass(git_odb_write(&id, odb, PACKED_STR, CONST_STRLEN(PACKED_STR), GIT_OBJ_BLOB)); cl_assert_equal_oid(&expected_id, &id); cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); cl_assert(before.st_atime == after.st_atime); cl_assert(before.st_atime_nsec == after.st_atime_nsec); cl_assert(before.st_mtime == after.st_mtime); cl_assert(before.st_mtime_nsec == after.st_mtime_nsec); }
void test_odb_backend_nobackend__write_fails_gracefully(void) { git_oid id; git_odb *odb; const git_error *err; git_repository_odb(&odb, _repo); cl_git_fail(git_odb_write(&id, odb, "Hello world!\n", 13, GIT_OBJ_BLOB)); err = giterr_last(); cl_assert_equal_s(err->message, "cannot write object - unsupported in the loaded odb backends"); git_odb_free(odb); }
static void commit_result(char* msg, git_oid *commit){ git_odb *odb; check_lg2(git_repository_odb(&odb, repo), "Could not allocate odb poiner", NULL); check_lg2(git_odb_write(commit, odb, &msg[PREAMBLE_LENGTH], MSG_LENGTH, GIT_OBJ_COMMIT), "Error writing commit", NULL); check_lg2(git_reference_create(NULL, repo, "refs/heads/master", commit, 1), "Could not update head", NULL); git_odb_free(odb); }
void test_clone_nonetwork__clone_tag_to_tree(void) { git_repository *stage; git_index_entry entry; git_index *index; git_odb *odb; git_oid tree_id; git_tree *tree; git_reference *tag; git_tree_entry *tentry; const char *file_path = "some/deep/path.txt"; const char *file_content = "some content\n"; const char *tag_name = "refs/tags/tree-tag"; stage = cl_git_sandbox_init("testrepo.git"); cl_git_pass(git_repository_odb(&odb, stage)); cl_git_pass(git_index_new(&index)); memset(&entry, 0, sizeof(git_index_entry)); entry.path = file_path; entry.mode = GIT_FILEMODE_BLOB; cl_git_pass(git_odb_write(&entry.id, odb, file_content, strlen(file_content), GIT_OBJ_BLOB)); cl_git_pass(git_index_add(index, &entry)); cl_git_pass(git_index_write_tree_to(&tree_id, index, stage)); cl_git_pass(git_reference_create(&tag, stage, tag_name, &tree_id, 0, NULL)); git_reference_free(tag); git_odb_free(odb); git_index_free(index); g_options.local = GIT_CLONE_NO_LOCAL; cl_git_pass(git_clone(&g_repo, cl_git_path_url(git_repository_path(stage)), "./foo", &g_options)); git_repository_free(stage); cl_git_pass(git_reference_lookup(&tag, g_repo, tag_name)); cl_git_pass(git_tree_lookup(&tree, g_repo, git_reference_target(tag))); git_reference_free(tag); cl_git_pass(git_tree_entry_bypath(&tentry, tree, file_path)); git_tree_entry_free(tentry); git_tree_free(tree); cl_fixture_cleanup("testrepo.git"); }
void test_odb_foreach__interrupt_foreach(void) { int nobj = 0; git_oid id; cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); git_repository_odb(&_odb, _repo); cl_assert_equal_i(-321, git_odb_foreach(_odb, foreach_stop_cb, &nobj)); cl_assert(nobj == 1000); git_odb_free(_odb); git_repository_free(_repo); cl_git_pass(git_repository_init(&_repo, "onlyloose.git", true)); git_repository_odb(&_odb, _repo); cl_git_pass(git_odb_write(&id, _odb, "", 0, GIT_OBJECT_BLOB)); cl_assert_equal_i(-123, git_odb_foreach(_odb, foreach_stop_first_cb, &nobj)); }
int git_commit_create_with_signature( git_oid *out, git_repository *repo, const char *commit_content, const char *signature, const char *signature_field) { git_odb *odb; int error = 0; const char *field; const char *header_end; git_buf commit = GIT_BUF_INIT; /* We start by identifying the end of the commit header */ header_end = strstr(commit_content, "\n\n"); if (!header_end) { giterr_set(GITERR_INVALID, "malformed commit contents"); return -1; } field = signature_field ? signature_field : "gpgsig"; /* The header ends after the first LF */ header_end++; git_buf_put(&commit, commit_content, header_end - commit_content); format_header_field(&commit, field, signature); git_buf_puts(&commit, header_end); if (git_buf_oom(&commit)) return -1; if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) goto cleanup; if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0) goto cleanup; cleanup: git_buf_dispose(&commit); return error; }
static int write_symlink( git_oid *id, git_odb *odb, const char *path, size_t link_size) { char *link_data; ssize_t read_len; int error; link_data = git__malloc(link_size); GITERR_CHECK_ALLOC(link_data); read_len = p_readlink(path, link_data, link_size); if (read_len != (ssize_t)link_size) { giterr_set(GITERR_OS, "failed to create blob: cannot read symlink '%s'", path); git__free(link_data); return -1; } error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJECT_BLOB); git__free(link_data); return error; }
static int write_file_filtered( git_oid *id, git_off_t *size, git_odb *odb, const char *full_path, git_filter_list *fl) { int error; git_buf tgt = GIT_BUF_INIT; error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path); /* Write the file to disk if it was properly filtered */ if (!error) { *size = tgt.size; error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJECT_BLOB); } git_buf_dispose(&tgt); return error; }
void test_odb_largefiles__write_from_memory(void) { git_oid expected, oid; git_buf buf = GIT_BUF_INIT; size_t i; #ifndef GIT_ARCH_64 cl_skip(); #endif if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || !cl_is_env_set("GITTEST_SLOW")) cl_skip(); for (i = 0; i < (3041*126103); i++) cl_git_pass(git_buf_puts(&buf, "Hello, world.\n")); git_oid_fromstr(&expected, "3fb56989cca483b21ba7cb0a6edb229d10e1c26c"); cl_git_pass(git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJ_BLOB)); cl_assert_equal_oid(&expected, &oid); }
void test_pack_indexer__fix_thin(void) { git_indexer *idx = NULL; git_transfer_progress stats = { 0 }; git_repository *repo; git_odb *odb; git_oid id, should_id; cl_git_pass(git_repository_init(&repo, "thin.git", true)); cl_git_pass(git_repository_odb(&odb, repo)); /* Store the missing base into your ODB so the indexer can fix the pack */ cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJ_BLOB)); git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); cl_assert_equal_oid(&should_id, &id); cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL, NULL)); cl_git_pass(git_indexer_append(idx, thin_pack, thin_pack_len, &stats)); cl_git_pass(git_indexer_commit(idx, &stats)); cl_assert_equal_i(stats.total_objects, 2); cl_assert_equal_i(stats.received_objects, 2); cl_assert_equal_i(stats.indexed_objects, 2); cl_assert_equal_i(stats.local_objects, 1); git_oid_fromstr(&should_id, "fefdb2d740a3a6b6c03a0c7d6ce431c6d5810e13"); cl_assert_equal_oid(&should_id, git_indexer_hash(idx)); git_indexer_free(idx); git_odb_free(odb); git_repository_free(repo); /* * The pack's name/hash only tells us what objects there are, * so we need to go through the packfile again in order to * figure out whether we calculated the trailer correctly. */ { unsigned char buffer[128]; int fd; ssize_t read; struct stat st; const char *name = "pack-fefdb2d740a3a6b6c03a0c7d6ce431c6d5810e13.pack"; fd = p_open(name, O_RDONLY); cl_assert(fd != -1); cl_git_pass(p_stat(name, &st)); cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL)); read = p_read(fd, buffer, sizeof(buffer)); cl_assert(read != -1); p_close(fd); cl_git_pass(git_indexer_append(idx, buffer, read, &stats)); cl_git_pass(git_indexer_commit(idx, &stats)); cl_assert_equal_i(stats.total_objects, 3); cl_assert_equal_i(stats.received_objects, 3); cl_assert_equal_i(stats.indexed_objects, 3); cl_assert_equal_i(stats.local_objects, 0); git_indexer_free(idx); } }
int main (int argc, char** argv) { // ### Opening the Repository // There are a couple of methods for opening a repository, this being the simplest. // There are also [methods][me] for specifying the index file and work tree locations, here // we are assuming they are in the normal places. // // [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository git_repository *repo; if (argc > 1) { git_repository_open(&repo, argv[1]); } else { git_repository_open(&repo, "/opt/libgit2-test/.git"); } // ### SHA-1 Value Conversions // For our first example, we will convert a 40 character hex value to the 20 byte raw SHA1 value. printf("*Hex to Raw*\n"); char hex[] = "fd6e612585290339ea8bf39c692a7ff6a29cb7c3"; // The `git_oid` is the structure that keeps the SHA value. We will use this throughout the example // for storing the value of the current SHA key we're working with. git_oid oid; git_oid_fromstr(&oid, hex); // Once we've converted the string into the oid value, we can get the raw value of the SHA. printf("Raw 20 bytes: [%.20s]\n", (&oid)->id); // Next we will convert the 20 byte raw SHA1 value to a human readable 40 char hex value. printf("\n*Raw to Hex*\n"); char out[41]; out[40] = '\0'; // If you have a oid, you can easily get the hex value of the SHA as well. git_oid_fmt(out, &oid); printf("SHA hex string: %s\n", out); // ### Working with the Object Database // **libgit2** provides [direct access][odb] to the object database. // The object database is where the actual objects are stored in Git. For // working with raw objects, we'll need to get this structure from the // repository. // [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb git_odb *odb; git_repository_odb(&odb, repo); // #### Raw Object Reading printf("\n*Raw Object Read*\n"); git_odb_object *obj; git_otype otype; const unsigned char *data; const char *str_type; int error; // We can read raw objects directly from the object database if we have the oid (SHA) // of the object. This allows us to access objects without knowing thier type and inspect // the raw bytes unparsed. error = git_odb_read(&obj, odb, &oid); // A raw object only has three properties - the type (commit, blob, tree or tag), the size // of the raw data and the raw, unparsed data itself. For a commit or tag, that raw data // is human readable plain ASCII text. For a blob it is just file contents, so it could be // text or binary data. For a tree it is a special binary format, so it's unlikely to be // hugely helpful as a raw object. data = (const unsigned char *)git_odb_object_data(obj); otype = git_odb_object_type(obj); // We provide methods to convert from the object type which is an enum, to a string // representation of that value (and vice-versa). str_type = git_object_type2string(otype); printf("object length and type: %d, %s\n", (int)git_odb_object_size(obj), str_type); // For proper memory management, close the object when you are done with it or it will leak // memory. git_odb_object_free(obj); // #### Raw Object Writing printf("\n*Raw Object Write*\n"); // You can also write raw object data to Git. This is pretty cool because it gives you // direct access to the key/value properties of Git. Here we'll write a new blob object // that just contains a simple string. Notice that we have to specify the object type as // the `git_otype` enum. git_odb_write(&oid, odb, "test data", sizeof("test data") - 1, GIT_OBJ_BLOB); // Now that we've written the object, we can check out what SHA1 was generated when the // object was written to our database. git_oid_fmt(out, &oid); printf("Written Object: %s\n", out); // ### Object Parsing // libgit2 has methods to parse every object type in Git so you don't have to work directly // with the raw data. This is much faster and simpler than trying to deal with the raw data // yourself. // #### Commit Parsing // [Parsing commit objects][pco] is simple and gives you access to all the data in the commit // - the // author (name, email, datetime), committer (same), tree, message, encoding and parent(s). // [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit printf("\n*Commit Parsing*\n"); git_commit *commit; git_oid_fromstr(&oid, "f0877d0b841d75172ec404fc9370173dfffc20d1"); error = git_commit_lookup(&commit, repo, &oid); const git_signature *author, *cmtter; const char *message; time_t ctime; unsigned int parents, p; // Each of the properties of the commit object are accessible via methods, including commonly // needed variations, such as `git_commit_time` which returns the author time and `_message` // which gives you the commit message. message = git_commit_message(commit); author = git_commit_author(commit); cmtter = git_commit_committer(commit); ctime = git_commit_time(commit); // The author and committer methods return [git_signature] structures, which give you name, email // and `when`, which is a `git_time` structure, giving you a timestamp and timezone offset. printf("Author: %s (%s)\n", author->name, author->email); // Commits can have zero or more parents. The first (root) commit will have no parents, most commits // will have one, which is the commit it was based on, and merge commits will have two or more. // Commits can technically have any number, though it's pretty rare to have more than two. parents = git_commit_parentcount(commit); for (p = 0;p < parents;p++) { git_commit *parent; git_commit_parent(&parent, commit, p); git_oid_fmt(out, git_commit_id(parent)); printf("Parent: %s\n", out); git_commit_free(parent); } // Don't forget to close the object to prevent memory leaks. You will have to do this for // all the objects you open and parse. git_commit_free(commit); // #### Writing Commits // // libgit2 provides a couple of methods to create commit objects easily as well. There are four // different create signatures, we'll just show one of them here. You can read about the other // ones in the [commit API docs][cd]. // [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit printf("\n*Commit Writing*\n"); git_oid tree_id, parent_id, commit_id; git_tree *tree; git_commit *parent; // Creating signatures for an authoring identity and time is pretty simple - you will need to have // this to create a commit in order to specify who created it and when. Default values for the name // and email should be found in the `user.name` and `user.email` configuration options. See the `config` // section of this example file to see how to access config values. git_signature_new((git_signature **)&author, "Scott Chacon", "*****@*****.**", 123456789, 60); git_signature_new((git_signature **)&cmtter, "Scott A Chacon", "*****@*****.**", 987654321, 90); // Commit objects need a tree to point to and optionally one or more parents. Here we're creating oid // objects to create the commit with, but you can also use git_oid_fromstr(&tree_id, "28873d96b4e8f4e33ea30f4c682fd325f7ba56ac"); git_tree_lookup(&tree, repo, &tree_id); git_oid_fromstr(&parent_id, "f0877d0b841d75172ec404fc9370173dfffc20d1"); git_commit_lookup(&parent, repo, &parent_id); // Here we actually create the commit object with a single call with all the values we need to create // the commit. The SHA key is written to the `commit_id` variable here. git_commit_create_v( &commit_id, /* out id */ repo, NULL, /* do not update the HEAD */ author, cmtter, NULL, /* use default message encoding */ "example commit", tree, 1, parent); // Now we can take a look at the commit SHA we've generated. git_oid_fmt(out, &commit_id); printf("New Commit: %s\n", out); // #### Tag Parsing // You can parse and create tags with the [tag management API][tm], which functions very similarly // to the commit lookup, parsing and creation methods, since the objects themselves are very similar. // [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag printf("\n*Tag Parsing*\n"); git_tag *tag; const char *tmessage, *tname; git_otype ttype; // We create an oid for the tag object if we know the SHA and look it up in the repository the same // way that we would a commit (or any other) object. git_oid_fromstr(&oid, "bc422d45275aca289c51d79830b45cecebff7c3a"); error = git_tag_lookup(&tag, repo, &oid); // Now that we have the tag object, we can extract the information it generally contains: the target // (usually a commit object), the type of the target object (usually 'commit'), the name ('v1.0'), // the tagger (a git_signature - name, email, timestamp), and the tag message. git_tag_target((git_object **)&commit, tag); tname = git_tag_name(tag); // "test" ttype = git_tag_type(tag); // GIT_OBJ_COMMIT (otype enum) tmessage = git_tag_message(tag); // "tag message\n" printf("Tag Message: %s\n", tmessage); git_commit_free(commit); // #### Tree Parsing // [Tree parsing][tp] is a bit different than the other objects, in that we have a subtype which is the // tree entry. This is not an actual object type in Git, but a useful structure for parsing and // traversing tree entries. // // [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree printf("\n*Tree Parsing*\n"); const git_tree_entry *entry; git_object *objt; // Create the oid and lookup the tree object just like the other objects. git_oid_fromstr(&oid, "2a741c18ac5ff082a7caaec6e74db3075a1906b5"); git_tree_lookup(&tree, repo, &oid); // Getting the count of entries in the tree so you can iterate over them if you want to. int cnt = git_tree_entrycount(tree); // 3 printf("tree entries: %d\n", cnt); entry = git_tree_entry_byindex(tree, 0); printf("Entry name: %s\n", git_tree_entry_name(entry)); // "hello.c" // You can also access tree entries by name if you know the name of the entry you're looking for. entry = git_tree_entry_byname(tree, "hello.c"); git_tree_entry_name(entry); // "hello.c" // Once you have the entry object, you can access the content or subtree (or commit, in the case // of submodules) that it points to. You can also get the mode if you want. git_tree_entry_to_object(&objt, repo, entry); // blob // Remember to close the looked-up object once you are done using it git_object_free(objt); // #### Blob Parsing // // The last object type is the simplest and requires the least parsing help. Blobs are just file // contents and can contain anything, there is no structure to it. The main advantage to using the // [simple blob api][ba] is that when you're creating blobs you don't have to calculate the size // of the content. There is also a helper for reading a file from disk and writing it to the db and // getting the oid back so you don't have to do all those steps yourself. // // [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob printf("\n*Blob Parsing*\n"); git_blob *blob; git_oid_fromstr(&oid, "af7574ea73f7b166f869ef1a39be126d9a186ae0"); git_blob_lookup(&blob, repo, &oid); // You can access a buffer with the raw contents of the blob directly. // Note that this buffer may not be contain ASCII data for certain blobs (e.g. binary files): // do not consider the buffer a NULL-terminated string, and use the `git_blob_rawsize` attribute to // find out its exact size in bytes printf("Blob Size: %ld\n", git_blob_rawsize(blob)); // 8 git_blob_rawcontent(blob); // "content" // ### Revwalking // // The libgit2 [revision walking api][rw] provides methods to traverse the directed graph created // by the parent pointers of the commit objects. Since all commits point back to the commit that // came directly before them, you can walk this parentage as a graph and find all the commits that // were ancestors of (reachable from) a given starting point. This can allow you to create `git log` // type functionality. // // [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk printf("\n*Revwalking*\n"); git_revwalk *walk; git_commit *wcommit; git_oid_fromstr(&oid, "f0877d0b841d75172ec404fc9370173dfffc20d1"); // To use the revwalker, create a new walker, tell it how you want to sort the output and then push // one or more starting points onto the walker. If you want to emulate the output of `git log` you // would push the SHA of the commit that HEAD points to into the walker and then start traversing them. // You can also 'hide' commits that you want to stop at or not see any of their ancestors. So if you // want to emulate `git log branch1..branch2`, you would push the oid of `branch2` and hide the oid // of `branch1`. git_revwalk_new(&walk, repo); git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); git_revwalk_push(walk, &oid); const git_signature *cauth; const char *cmsg; // Now that we have the starting point pushed onto the walker, we can start asking for ancestors. It // will return them in the sorting order we asked for as commit oids. // We can then lookup and parse the commited pointed at by the returned OID; // note that this operation is specially fast since the raw contents of the commit object will // be cached in memory while ((git_revwalk_next(&oid, walk)) == 0) { error = git_commit_lookup(&wcommit, repo, &oid); cmsg = git_commit_message(wcommit); cauth = git_commit_author(wcommit); printf("%s (%s)\n", cmsg, cauth->email); git_commit_free(wcommit); } // Like the other objects, be sure to free the revwalker when you're done to prevent memory leaks. // Also, make sure that the repository being walked it not deallocated while the walk is in // progress, or it will result in undefined behavior git_revwalk_free(walk); // ### Index File Manipulation // // The [index file API][gi] allows you to read, traverse, update and write the Git index file // (sometimes thought of as the staging area). // // [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index printf("\n*Index Walking*\n"); git_index *index; unsigned int i, ecount; // You can either open the index from the standard location in an open repository, as we're doing // here, or you can open and manipulate any index file with `git_index_open_bare()`. The index // for the repository will be located and loaded from disk. git_repository_index(&index, repo); // For each entry in the index, you can get a bunch of information including the SHA (oid), path // and mode which map to the tree objects that are written out. It also has filesystem properties // to help determine what to inspect for changes (ctime, mtime, dev, ino, uid, gid, file_size and flags) // All these properties are exported publicly in the `git_index_entry` struct ecount = git_index_entrycount(index); for (i = 0; i < ecount; ++i) { git_index_entry *e = git_index_get(index, i); printf("path: %s\n", e->path); printf("mtime: %d\n", (int)e->mtime.seconds); printf("fs: %d\n", (int)e->file_size); } git_index_free(index); // ### References // // The [reference API][ref] allows you to list, resolve, create and update references such as // branches, tags and remote references (everything in the .git/refs directory). // // [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference printf("\n*Reference Listing*\n"); // Here we will implement something like `git for-each-ref` simply listing out all available // references and the object SHA they resolve to. git_strarray ref_list; git_reference_list(&ref_list, repo, GIT_REF_LISTALL); const char *refname; git_reference *ref; // Now that we have the list of reference names, we can lookup each ref one at a time and // resolve them to the SHA, then print both values out. for (i = 0; i < ref_list.count; ++i) { refname = ref_list.strings[i]; git_reference_lookup(&ref, repo, refname); switch (git_reference_type(ref)) { case GIT_REF_OID: git_oid_fmt(out, git_reference_oid(ref)); printf("%s [%s]\n", refname, out); break; case GIT_REF_SYMBOLIC: printf("%s => %s\n", refname, git_reference_target(ref)); break; default: fprintf(stderr, "Unexpected reference type\n"); exit(1); } } git_strarray_free(&ref_list); // ### Config Files // // The [config API][config] allows you to list and updatee config values in // any of the accessible config file locations (system, global, local). // // [config]: http://libgit2.github.com/libgit2/#HEAD/group/config printf("\n*Config Listing*\n"); const char *email; int32_t j; git_config *cfg; // Open a config object so we can read global values from it. git_config_open_ondisk(&cfg, "~/.gitconfig"); git_config_get_int32(cfg, "help.autocorrect", &j); printf("Autocorrect: %d\n", j); git_config_get_string(cfg, "user.email", &email); printf("Email: %s\n", email); // Finally, when you're done with the repository, you can free it as well. git_repository_free(repo); return 0; }
static int git_commit__create_internal( git_oid *id, git_repository *repo, const char *update_ref, const git_signature *author, const git_signature *committer, const char *message_encoding, const char *message, const git_oid *tree, git_commit_parent_callback parent_cb, void *parent_payload, bool validate) { git_reference *ref = NULL; int error = 0, matched_parent = 0; const git_oid *current_id = NULL; git_buf commit = GIT_BUF_INIT; size_t i = 0; git_odb *odb; const git_oid *parent; assert(id && repo && tree && parent_cb); if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE)) return -1; if (update_ref) { error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); if (error < 0 && error != GIT_ENOTFOUND) return error; } giterr_clear(); if (ref) current_id = git_reference_target(ref); git_oid__writebuf(&commit, "tree ", tree); while ((parent = parent_cb(i, parent_payload)) != NULL) { if (validate && !git_object__is_valid(repo, parent, GIT_OBJ_COMMIT)) { error = -1; goto on_error; } git_oid__writebuf(&commit, "parent ", parent); if (i == 0 && current_id && git_oid_equal(current_id, parent)) matched_parent = 1; i++; } if (ref && !matched_parent) { git_reference_free(ref); git_buf_free(&commit); giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent"); return GIT_EMODIFIED; } git_signature__writebuf(&commit, "author ", author); git_signature__writebuf(&commit, "committer ", committer); if (message_encoding != NULL) git_buf_printf(&commit, "encoding %s\n", message_encoding); git_buf_putc(&commit, '\n'); if (git_buf_puts(&commit, message) < 0) goto on_error; if (git_repository_odb__weakptr(&odb, repo) < 0) goto on_error; if (git_odb_write(id, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0) goto on_error; git_buf_free(&commit); if (update_ref != NULL) { error = git_reference__update_for_commit( repo, ref, update_ref, id, "commit"); git_reference_free(ref); return error; } return 0; on_error: git_buf_free(&commit); return -1; }
return -1; if ((o1->len > 0) && (memcmp(o1->data, o2->data, o1->len) != 0)) return -1; return 0; } BEGIN_TEST(write_commit) git_odb *db; git_oid id1, id2; git_obj obj; must_pass(make_odb_dir()); must_pass(git_odb_open(&db, odb_dir)); must_pass(git_oid_mkstr(&id1, commit.id)); must_pass(git_odb_write(&id2, db, &commit_obj)); must_be_true(git_oid_cmp(&id1, &id2) == 0); must_pass(check_object_files(&commit)); must_pass(git_odb__read_loose(&obj, db, &id1)); must_pass(cmp_objects(&obj, &commit_obj)); git_obj_close(&obj); git_odb_close(db); must_pass(remove_object_files(&commit)); END_TEST BEGIN_TEST(write_tree) git_odb *db; git_oid id1, id2; git_obj obj;
int main(int argc, char **argv) { git_repository *repo; git_odb *odb; git_oid oid, tree_id, commit; git_index *index; char hash[41]; int error; char *folder; int folder_size; git_signature *author, *committer; git_tree *tree; git_reference *master; hash[40] = '\0'; if(argc != 2) { printf("usage: migit <path-to-git-repo>\n"); return 1; } folder_size = strlen(argv[1]) + 6; folder = malloc(folder_size); sprintf(folder, "%s/.git", argv[1]); folder[folder_size - 1] = '\0'; create_sample_file(argv[1]); error = git_repository_open(&repo, folder); if(error) { die("could not open repository", error); } odb = git_repository_database(repo); git_odb_write(&oid, odb, content, strlen(content) * sizeof(char), GIT_OBJ_BLOB); error = git_repository_index(&index, repo); if(error) { die("could not open index", error); } error = git_index_add(index, fname, 0); if(error) { die("could not add file to index", error); } assert(git_index_entrycount(index) == 1); error = git_tree_create_fromindex(&tree_id, index); if(error) { die("could not create tree from index", error); } error = git_tree_lookup(&tree, repo, &tree_id); if(error) { die("could not create tree from id", error); } error = git_signature_new(&author, "helino", "*****@*****.**", 12345, 0); if(error) { die("could not create signature for author", error); } error = git_signature_new(&committer, "irock", "*****@*****.**", 12345, 0); if(error) { die("could not create signature for committer", error); } error = git_commit_create(&commit, repo, NULL, author, committer, NULL, "hello git", tree, 0, NULL); if(error) { die("could not commit", error); } git_oid_fmt(hash, &commit); printf("commit: %s\n", hash); error = git_reference_create_oid(&master, repo, "refs/heads/master", &commit, 1); if(error) { die("could not create master reference", error); } git_repository_free(repo); free(folder); return 0; }
int git_commit_create( git_oid *oid, git_repository *repo, const char *update_ref, const git_signature *author, const git_signature *committer, const char *message_encoding, const char *message, const git_tree *tree, int parent_count, const git_commit *parents[]) { git_buf commit = GIT_BUF_INIT; int error, i; if (git_object_owner((const git_object *)tree) != repo) return git__throw(GIT_EINVALIDARGS, "The given tree does not belong to this repository"); git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree)); for (i = 0; i < parent_count; ++i) { if (git_object_owner((const git_object *)parents[i]) != repo) { error = git__throw(GIT_EINVALIDARGS, "The given parent does not belong to this repository"); goto cleanup; } git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i])); } git_signature__writebuf(&commit, "author ", author); git_signature__writebuf(&commit, "committer ", committer); if (message_encoding != NULL) git_buf_printf(&commit, "encoding %s\n", message_encoding); git_buf_putc(&commit, '\n'); git_buf_puts(&commit, message); if (git_buf_oom(&commit)) { error = git__throw(GIT_ENOMEM, "Not enough memory to build the commit data"); goto cleanup; } error = git_odb_write(oid, git_repository_database(repo), commit.ptr, commit.size, GIT_OBJ_COMMIT); git_buf_free(&commit); if (error == GIT_SUCCESS && update_ref != NULL) { git_reference *head; error = git_reference_lookup(&head, repo, update_ref); if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to create commit"); error = git_reference_resolve(&head, head); if (error < GIT_SUCCESS) { if (error != GIT_ENOTFOUND) return git__rethrow(error, "Failed to create commit"); /* * The target of the reference was not found. This can happen * just after a repository has been initialized (the master * branch doesn't exist yet, as it doesn't have anything to * point to) or after an orphan checkout, so if the target * branch doesn't exist yet, create it and return. */ return git_reference_create_oid(&head, repo, git_reference_target(head), oid, 1); } error = git_reference_set_oid(head, oid); } if (error < GIT_SUCCESS) return git__rethrow(error, "Failed to create commit"); return GIT_SUCCESS; cleanup: git_buf_free(&commit); return error; }
static int write_index(git_oid *oid, git_index *index, const char *base, int baselen, int entry_no, int maxentries) { size_t size, offset; char *buffer; int nr, error; /* Guess at some random initial size */ size = maxentries * 40; buffer = git__malloc(size); if (buffer == NULL) return GIT_ENOMEM; offset = 0; for (nr = entry_no; nr < maxentries; ++nr) { git_index_entry *entry = git_index_get(index, nr); const char *pathname = entry->path, *filename, *dirname; int pathlen = strlen(pathname), entrylen; unsigned int write_mode; git_oid subtree_oid; git_oid *write_oid; /* Did we hit the end of the directory? Return how many we wrote */ if (baselen >= pathlen || memcmp(base, pathname, baselen) != 0) break; /* Do we have _further_ subdirectories? */ filename = pathname + baselen; dirname = strchr(filename, '/'); write_oid = &entry->oid; write_mode = entry->mode; if (dirname) { int subdir_written; #if 0 if (entry->mode != S_IFDIR) { free(buffer); return GIT_EOBJCORRUPTED; } #endif subdir_written = write_index(&subtree_oid, index, pathname, dirname - pathname + 1, nr, maxentries); if (subdir_written < GIT_SUCCESS) { free(buffer); return subdir_written; } nr = subdir_written - 1; /* Now we need to write out the directory entry into this tree.. */ pathlen = dirname - pathname; write_oid = &subtree_oid; write_mode = S_IFDIR; } entrylen = pathlen - baselen; if (offset + entrylen + 32 > size) { size = alloc_nr(offset + entrylen + 32); buffer = git__realloc(buffer, size); if (buffer == NULL) return GIT_ENOMEM; } offset += write_index_entry(buffer + offset, write_mode, filename, entrylen, write_oid); } error = git_odb_write(oid, index->repository->db, buffer, offset, GIT_OBJ_TREE); free(buffer); return (error == GIT_SUCCESS) ? nr : error; }