I need to use libgit2 to implement the "git commit -F ..." command.
The code below will commit 1.txt and 2.txt:
git_libgit2_init();
git_repository* pRepository;
git_index* pIndex;
git_oid oidTree, oidCommitted;
git_tree* pTree;
git_signature* pSignature;
git_repository_init(&pRepository, "C:\\Temp", false);
git_repository_index(&pIndex, pRepository);
git_index_add_bypath(pIndex, "1.txt");
git_index_add_bypath(pIndex, "2.txt");
git_index_write(pIndex);
git_index_write_tree(&oidTree, pIndex);
git_tree_lookup(&pTree, pRepository, &oidTree);
git_signature_now(&pSignature, "My name", "My email");
git_commit_create(&oidCommitted, pRepository, "refs/heads/master",
pSignature, pSignature, NULL, "Initial commit with 1.txt", pTree, 0, NULL);
git_signature_free(pSignature);
git_tree_free(pTree);
git_index_free(pIndex);
git_repository_free(pRepository);
git_libgit2_shutdown();
How to change my code to implement:
git add 1.txt 2.txt
git commit 1.txt -m "Initial commit with 1.txt"
You want to come up with a tree object to pass to git_commit_create without modifying the repository's index. There are several ways to do that. The easiest is to create your own in-memory index and use that instead of the repository's index. Essentially, do something like this:
...
git_repository_init(&repo, ...); // same as before
git_index_new(&index); // create in-memory index
git_index_read_tree(index, headTree); // initialize to the current HEAD
git_index_add_by_path(index, "1.txt"); // update the nominated file(s)
git_index_write_tree_to(&oid, index, repo); // write the tree into the repo
git_tree_lookup(&tree, repo, &oid); // same as before
...
Method One:
Either modify the repository's index to reflect what you want to commit: call git_index_add to put the data for 2.txt back in the index, then remove it again after creating the tree for the commit.
Method Two:
Duplicate the repository's index as a temporary, in-memory index and mutate that one, adding the 1.txt back in. Then create the tree from that index instead of the repository's index. git_index_add_by_path will fail when operating on an in-memory index. It can’t find that path, it has no concept of where to look for it.
Git_index_add_by_path works by reading the file on disk to create a git_index_entry. It’s a convenience function.
Instead, you can use git_index_add with the index entry data.
Related
As the docs of RENAME says:
Renames key to newkey. It returns an error when key does not exist. If newkey already exists it is overwritten, when this happens RENAME executes an implicit DEL operation, so if the deleted key contains a very big value it may cause high latency even if RENAME itself is usually a constant-time operation.
As we know, DEL is blocking while UNLINK is non-blocking.
So I have two questions:
If the deleted key contains a very big value, it seems that executing an implicit UNLINK would be better. Why redis determines to use DEL?
If I manully execute UNLINK then RENAME with transaction, will the high latency be avoided?
The "implicit DEL operation" is not the same as a DEL command called by a user.
You can config it to use async or sync delete. The reason behind it is to probably give the user more control.
In the redis config file, on the part of LAZY FREEING, it says
DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.
It's up to the design of the application to understand when it is a good
idea to use one or the other. However the Redis server sometimes has to
delete keys or flush the whole database as a side effect of other operations.**
Specifically Redis deletes objects independently of a user call in the
following scenarios:
....
For example the RENAME command may delete the old key content when it is replaced with >another one.
....
In all the above cases the default is to delete objects in a blocking way,
like if DEL was called. However you can configure each case specifically
in order to instead release memory in a non-blocking way like if UNLINK
was called, using the following configuration directives.
Then there's the config
lazyfree-lazy-server-del no
Just switch it to YES then it will behave like UNLINK
I checked the source code,
For Redis version 5.0, this function is called when you call RENAME command
void renameGenericCommand(client *c, int nx) {
// some code....
// When source and dest key is the same, no operation is performed,
// if the key exists, however we still return an error on unexisting key.
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1;
// some code ...
if (samekey) {
addReply(c,nx ? shared.czero : shared.ok);
return;
}
...
/* Overwrite: delete the old key before creating the new one
* with the same name. */
dbDelete(c->db,c->argv[2]);
}
This is the dbDelete function it called
int dbDelete(redisDb *db, robj *key) {
return server.lazyfree_lazy_server_del ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
As you can see, it does refer to the config of lazyfree-lazy-server-del
in git it would be
1. create file a.txt
2. commit file a.txt
3. a.txt is tracked
I can do that in libgit2 easily.
However, If I modify a.txt and wanted to add it to index I will do
git add a.txt
Unfortunately I cannot emulate that with libgit2. I have tried virtually everything the internet offered yet nothing worked. So I feel I miss something basic here. Note that I can add new files that aren't tracked to index with no problem, only I cannot find the way to add them.
Here is the code that I have.
void add_file(char *file)
{
git_index *index;
int error;
const git_index_entry *entry;
git_index_entry new_entry;
error = git_repository_index(&index, m_repo);
entry = git_index_get_bypath(index,file, 0);
if(entry)
{
memcpy(&new_entry, entry, sizeof(git_index_entry));
new_entry.path = file;
new_entry.mode = GIT_FILEMODE_BLOB;
error = git_index_add(index, &entry);
}
else
error = git_index_add_bypath(index, file);
error = git_index_write(index);
git_index_free(index);
}
EDIT:
After Ed's post I update my code but it is still adding only new files that aren't tracked.
git_index *index;
git_oid tree;
int error;
error = git_repository_index(&index, m_repo);
error = git_index_add_bypath(index, file);
error = git_index_write(index);
error = git_index_write_tree(&tree, index);
git_index_free(index);
Your git_index_get_bypath gives you the index entry as it exists currently. You're then memcpying the entry that is in the index and git_index_adding it right back. You're not actually changing the entry at all.
(That is, unless the index entry is currently executable. In which case you are removing the execute bit by setting the mode to GIT_FILEMODE_BLOB.)
You probably don't want to use git_index_add here, as that actually edits the low-level contents of the index directly. You would need to add the file to the object database and then update the index with the resultant OID.
If the file is changed on disk, then just run git_index_add_bypath and let libgit2 update the index with the contents as they exist on disk.
I'm working on implementing merge using libgit2, and I'm having trouble getting it to deal with conflicts (changes to the same line in a file) - the merge just aborts, with nothing written to the index or to the workspace. Resolvable conflicts (changes to different lines) are working fine.
It exits with GIT_ECONFLICT, which apparently indicates that the worktree and/or index aren't clean, but I checked with git status just before calling git_merge() and it's clean.
I'm using default merge options, and checkout options set to GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS. I tried using FORCE instead of SAFE but it didn't help. What else do I need to do so the conflicts are recorded?
Code is here (in Swift):
https://github.com/Uncommon/Xit/blob/ff1bf6312bb1250b1db432035947a282a2cdd362/Xit/XTRepository%2BMergePushPull.swift#L154
It turned out the problem was that, since my unit test had just done a git commit using the command line tool, libgit2's in-memory copy of the index was out of date, so using that old copy of the index it thought there was a conflict. Reloading the index with git_index_read() before calling git_merge() solved the problem.
This is actually a bug in libgit2; git_merge should be reloading the index itself: https://github.com/libgit2/libgit2/issues/4203
There is a sha value represent to the status for git repository in current status?
The sha value to be updated each time the Object Database updated and References changed if git has this sha value.
In other words, the sha value represent to the current version of whole repository.
Here is my code for calculate a sha for current references. There is a git level API or interface?
private string CalcBranchesSha(bool includeTags = false)
{
var sb = new StringBuilder();
sb.Append(":HEAD");
if (_repository.Head.Tip != null)
sb.Append(_repository.Head.Tip.Sha);
sb.Append(';');
foreach (var branch in _repository.Branches.OrderBy(s => s.Name))
{
sb.Append(':');
sb.Append(branch.Name);
if (branch.Tip != null)
sb.Append(branch.Tip.Sha);
}
sb.Append(';');
if (includeTags)
{
foreach (var tag in _repository.Tags.OrderBy(s => s.Name))
{
sb.Append(':');
sb.Append(tag.Name);
if (tag.Target != null)
sb.Append(tag.Target.Sha);
}
}
return sb.ToString().CalcSha();
}
There is a sha value represent to the status for git repository in current status?
In git parlance, the status of a repository usually refers to the paths that have differences between the working directory, the index and the current HEAD commit.
However, it doesn't look like you're after this. From what I understand, you're trying to calculate a checksum that represents the state of the current repository (ie. what your branches and tags point to).
Regarding the code, it may be improved in some ways to get a more precise checksum:
Account for all the references (beside refs/tags and refs/heads) in the repository (think refs/stash, refs/notes or the generated backup references in refs/original when one rewrite the history of a repository).
Consider disambiguating symbolic from direct references. (ie: HEAD->master->08a4217 should lead to a different checksum than HEAD->08a4127)
Below a modified version of the code which deals with those two points above, by leveraging the Refs namespace:
private string CalculateRepositoryStateSha(IRepository repo)
{
var sb = new StringBuilder();
sb.Append(":HEAD");
sb.Append(repo.Refs.Head.TargetIdentifier);
sb.Append(';');
foreach (var reference in repo.Refs.OrderBy(r => r.CanonicalName))
{
sb.Append(':');
sb.Append(reference.CanonicalName);
sb.Append(reference.TargetIdentifier);
sb.Append(';');
}
return sb.ToString().CalcSha();
}
Please keep in mind the following limits:
This doesn't consider the changes to the index or the workdir (eg. the same checksum will be returned either a file has been staged or not)
There are ways to create object in the object database without modifying the references. Those kind of changes won't be reflected with the code above. One possible hack-ish way to do this could be to also append to the StringBuuilder, the number of object in the object database (ie. repo.ObjectDatabase.Count()), but that may hinder the overall performance as this will enumerate all the objects each time the checksum is calculated).
There is a git level API or interface?
I don't know of any equivalent native function in git (although similar result may be achieved through some scripting). There's nothing native in libgit2 or Libgit2Sharp APIs.
I'm using the v3 API and managed to list repos/trees/branches, access file contents, and create blobs/trees/commits. I'm now trying to create a new repo, and managed to do it with "POST user/repos"
But when I try to create blobs/trees/commits/references in this new repo I get the same error message. (409) "Git Repository is empty.". Obviously I can go and init the repository myself through the git command line, but would rather like if my application did it for me.
Is there a way to do that? What's the first thing I need to do through the API after I create an empty repository?
Thanks
Since 2012, it is now possible to auto initialize a repository after creation, according to this blog post published on the GitHub blog:
Today we’ve made it easier to add commits to a repository via the GitHub API. Until now, you could create a repository, but you would need to initialize it locally via your Git client before adding any commits via the API.
Now you can optionally init a repository when it’s created by sending true for the auto_init parameter:
curl -i -u pengwynn \
-d '{"name": "create-repo-test", "auto_init": true}' \
https://api.github.com/user/repos
The resulting repository will have a README stub and an initial commit.
Update May 2013: Note that the repository content API now authorize adding files.
See "File CRUD and repository statistics now available in the API".
Original answer (May 2012)
Since it doesn't seems to be supported yet ("GitHub v3 API: How to create initial commit for my shiny new repository?", as aclark comments), you can start by pushing an initial empty commit
git commit --allow-empty -m 'Initial commit'
git push origin master
That can be a good practice to initialize one's repository anyway.
And it is illustrated in "git's semi-secret empty tree".
If you want to create an empty initial commit (i.e. one without any file) you can do the following:
Create the repository using the auto_init option as in Jai Pandya's answer; or, if the repository already exists, use the create file endpoint to create a dummy file - this will create the branch:
PUT https://api.github.com/repos/USER/REPO/contents/dummy
{
"branch": "master",
"message": "Create a dummy file for the sake of creating a branch",
"content": "ZHVtbXk="
}
This will give you a bunch of data including a commit SHA, but you can discard all of it since we are about to obliterate that commit.
Use the create commit endpoint to create a commit that points to the empty tree:
POST https://api.github.com/repos/USER/REPO/git/commits
{
"message": "Initial commit",
"tree": "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
}
This time you need to take note of the returned commit SHA.
Use the update reference endpoint to make the branch point to the commit you just created (notice the Use Of The ForceTM):
PATCH https://api.github.com/repos/USER/REPO/git/refs/heads/master
{
"sha": "<the SHA of the commit>",
"force": true
}
Done! Your repository has now one branch, one commit and zero files.
2023 January: The 2. step of Konamiman's solution did not work for me, unless I deleted the dummy file with the contents api:
DELETE https://api.github.com/repos/USER/REPO/contents/dummy
{
"branch": "master",
"message": "Delete dummy file",
"sha": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
}
(This sha is for the empty content "".)
After deleting the dummy file somehow 4b825dc642cb6eb9a060e54bf8d69288fbee4904 becomes assignable to a new commit.
It looks like over time this changed. It feels bad to rely on undocumented API behavior. :(