I am looking for a method to cherry pick two or more commits.
My goal is to be able to cherry pick multiple commits to allow a user to review those changes before committing them, and not requiring users to commit after each cherry pick.
I've added below a code snippet that will accept a repository path, followed by two commits and try to cherry pick them consecutively. However I'm not certain what options I need to set to allow two commits to be cherry picked.
As is the first cherry pick works, but the 2nd fails with
1 uncommitted change would be overwritten by merge
I had tried using the option GIT_CHECKOUT_ALLOW_CONFLICTS but was not successful. What options are needed to allow for cherry picking multiple commits?
#include <stdio.h>
#include "git2.h"
#define onError(error, errorMsg)\
if (error){\
const git_error* lg2err = giterr_last();\
if (lg2err){\
printf("%s %s\n", errorMsg, lg2err->message);\
return 1;\
}\
}
int main(int argc, char* argv[])
{
if(argc != 4) { printf("Provide repo commit1 commit2\n"); return 1;}
printf("Repository: %s\n Commit1: %s\n Commit2: %s\n", argv[1], argv[2], argv[3]);
int error;
git_libgit2_init();
git_repository * repo;
git_oid cid1, cid2;
git_commit *c1 =NULL;
git_commit *c2 =NULL;
error = git_repository_open(&repo, argv[1]);
onError(error,"Repo open failed: ");
git_cherrypick_options cherry_opts = GIT_CHERRYPICK_OPTIONS_INIT;
git_oid_fromstr(&cid1, argv[2]);
git_oid_fromstr(&cid2, argv[3]);
error = git_commit_lookup(&c1, repo, &cid1);
onError(error,"commit lookup failed: ");
error = git_commit_lookup(&c2, repo, &cid2);
onError(error,"commit2 lookup failed: ");
error = git_cherrypick(repo, c1, &cherry_opts);
onError(error,"cherry1 failed: ");
error = git_cherrypick(repo, c2, &cherry_opts);
onError(error,"cherry2 failed: ");
return 0;
}
What's happening is that libgit2 is refusing to overwrite a file on disk that has been modified, but its contents have not actually been stored anywhere by git. This file is "precious", and git and libgit2 will take great pains to avoid overwriting it.
There's no way to overcome this because cherry-picking is not applying the differences in the commit based on your working directory contents. It's applying the differences in the commit to HEAD. That is to say that your only options would be to ignore the changes in this cherry-pick or to overwrite the changes that the previous cherry-pick introduced.
Let me give you a concrete example:
Suppose that you have some file at commit 1:
one
two
three
four
five
And you have some commit based on 1 (let's call it 2), that changes the file to be:
one
2
three
four
five
And you have still another commit in a different branch. It's also based on 1 (let's call it 2'). It changes the file to be:
one
two
three
4
five
What happens if you are on commit 1 and cherry-pick both 2 and 2' without committing? Logically, you might expect it to do a merge! But it will not.
If you're on commit 1 and you git_cherrypick for commit 2 in libgit2 (or git cherry-pick --no-commit on the command line) for the first commit, it will read the file out of HEAD, and apply the changes for commit 2. This is a trivial example, so the contents are, literally, matching the contents of commit 2. That file will be placed on disk.
Now, if you do nothing else - you don't commit this - then you're still on commit 1. And if you again do a git_cherrypick (this time for commit 2') then libgit2 will read the file out of HEAD and apply the changes for commit 2'. And again, in this trivial example, applying the changes in 2' to the file in 1 gives you the contents of the file in commit 2'.
Because what it won't do is read the file out of the working directory.
So now when it goes to try to write those results to the working directory, there's a checkout conflict. Because the contents of the file on disk don't match the value of the file in HEAD or in what we're trying to checkout. So you're blocked.
What you probably want to do is create a commit at this stage. I know you said that you wanted t avoid "requiring users to commit after each cherry pick". But there's a difference between creating a commit object in libgit2 which is lightweight and can be discarded easily (where it will be garbage collected eventually) and doing the moral equivalent of running git commit which updates a branch pointer.
If you merely create a commit and write it into the object database - without switching to it or checking it out - then you can reuse that data for other steps in your work without ever giving the user the appearance of having done a commit. It's entirely in memory (and a little bit in the object database) without ever hitting the working directory.
What I'd encourage you to do is to cherry-pick each commit that you want into an index, which does its work in-memory and doesn't touch the disk. When you're happy with the results, you can create a commit object. You'll need to use the git_cherrypick_commit API instead of git_cherrypick to produce an index, then turn that into a tree for . For example:
git_reference *head;
git_signature *signature;
git_commit *base1, *base2, *result1;
git_index *idx1, *idx2;
git_oid tree1;
/* Look up the HEAD reference */
git_repository_head(&head, repo);
git_reference_peel((git_object **)&base1, head, GIT_OBJ_COMMIT);
/* Pick the first cherry, getting back an index */
git_cherrypick_commit(&idx1, repo, c1, base1, 0, &cherry_opts);
/* Write that index into a tree */
git_index_write_tree(&tree_id1, idx1);
/* And create a commit object for that tree */
git_signature_now(&signature, "My Cherry-Picking System", "foo#example.com");
git_commit_create_from_ids(&result_id1,
repo,
NULL, /* don't update a reference */
signature,
signature,
NULL,
"Transient commit that will be GC'd eventually.",
&tree_id1,
1,
&cid1);
git_commit_lookup(&result1, repo, &result_id1);
/* Now, you can pick the _second_ cherry with the commit you just created as a base... */
git_cherrypick_commit(&idx2, repo, c1, result1, 0, &cherry_opts);
Eventually you'll get your terminal commit and you can just check it out - and I mean that in the libgit2 git_checkout notion of checking out, which just puts those contents in your working directory. Still, don't update any branch pointers. This will give the result where files are only modified in the working directory (and index) but the user has not committed anything, their HEAD has not moved.
git_checkout_tree(repo, final_result_commit, NULL);
(You can pass a git_commit * to git_checkout_tree. It knows what to do.)
I could have made this a lot easier for you by giving you a git_cherrypick_tree API. This would let you cut out the middleman of creating a commit that you don't need. But I didn't think that anybody would want to do this. (Sorry!)
The reason that I didn't think that anybody would want to do this is because what you're describing is more accurately called rebase. Rebase is a sequenced set of patch application or cherry-pick steps. (Interactive rebase is a bit more involved, so let's ignore that for now.)
libgit2 has a git_rebase machinery that can work entirely in-memory, saving you some of the bookkeeping involved in converting indexes to trees and writing commits to disk. It can be invoked to work completely in-memory (see rebase_commit_inmemory) which may help you here.
In either case, the end result is largely the same, a series of commits that were written into the object database without the user ever knowing about it, and updating their working directory to match at the end.
Related
I 'm trying to pull from a repo with libgit2.
My steps are these:
I use git_remote_fetch to down remote origin data and the return OK.
after I use git_merge API.
The question is when I just use git_index_remove_bypath deleted a file 'aa.txt' in local branch 'master', But not commit it. At the same times I Merge remote branch('origin/master') head. the remote head just modify 'bb.txt'. But when I use git_merge it return error code '-13'. the error info is "1 uncommitted change would be overwritten by merge". I just deleted a file in my local branch.
But I can successed to execute in Git command line 'git pull'.
so, I suspect that my strategy is wrong when I execute Checkout. How to avoid this error?
I just deleted a file in my local branch.
If you only called git_index_remove_bypath, and you did not commit that change, then you have not deleted a file in your local branch. You have an uncommitted change.
That's why you're getting this error:
1 uncommitted change would be overwritten by merge
Commit the change, then do the merge. Or do the merge, then remove the file. But doing the merge in the state you're in is not possible because it would remove uncommitted changes.
I am experiencing a glitch from OpenDaylight (using Mininet).
Essentially, I am querying flow rules on specific nodes and on specific tables. The relevant code is the following, and is run by 1 separate thread per node that I am polling:
public static final InstanceIdentifier<Nodes NODES_II = InstanceIdentifier
.builder(Nodes.class).build();
public static InstanceIdentifier<Table> makeTableIId(NodeId nodeId, Short tableId) {
return NODES_IID.child(Node.class, new NodeKey(nodeId))
.augmentation(FlowCapableNode.class)
.child(Table.class, new TableKey(tableId));
}
and
InstanceIdentifier<Table> tableIId = makeTableIId(nodeId, tableId);
Optional<Table> tableOptional = dataBroker.newReadOnlyTransaction()
.read(LogicalDatastoreType.OPERATIONAL, tableIId).get();
if(!tableOptional.isPresent()) {
continue;
}
List<Flow> flows = tableOptional.get().getFlow();
The behavior: tableOptional is present, and getFlow() returns an empty list.
The observation: there ARE flow rules installed on ALL nodes on the tables I am querying, but for some reason, some of these nodes show none of these flows on none of the tables (here, tables 3, 4, 5, and 6).
The weirdness: On one of the problematic nodes, I have four rules, installed on tables 9, 13, 17 and 22 respectively. They timeout simultaneously after 150 seconds. After they disappear, the query suddenly begins to "see" the flows installed on tables 3, 4, 5, and 6, returning these for each table.
Question: How is this even possible?
EDIT I just realized that the rules whose timeout "suddenly fix everything" were also rules that generated warnings in ODL's log (OpenFlowPlugin to be more specific). I did not observe any obvious issue, so I'd sort of brushed it aside.
Here is the code relevant to the error:
https://pastebin.com/yJDZesXU
Here are the errors I get every time I install a rule that walks through these lines:
https://pastebin.com/c9HYLBt6
I must stress that these rules work as intended, and that printing them out reveals no evident formatting issue. Again, they appear fine when dumped.
My hypothesis is that this warning is a symptom of ODL "messing up" trying to store the rules in MD-SAL, which ends up messing a lot of rule-reading queries. On uninstallation of the garbage that ensues, rule-reading queries become functional again.
This makes sense to me, but then... I haven't understood how to fix these warnings, or what these warnings were about in the first place.
EDIT 2: By commenting lines suspecting of causing the warnings in the above pastebin:
//ipv4MatchBuilder.setIpv4SourceAddressNoMask(...);
//ipv4MatchBuilder.setIpv4SourcenArbitraryBitmask(...);
The warnings disappear, AND the flows appear correctly on all tables, when pinged. This confirms my hypothesis that somewhere, something wrong happens in the data store.
EDIT 3: I have found that by setting any non-trivial arbitrary bitmask, this error goes away. That is, I have tried setting an arbitrary bitmask which was neither null nor "255.255.255.255", and this error has gone away. The problem is I might like having a bitmask for the source, but an exact match on the destination. Even setting the bitmask to "127.255.255.255" (as I tried) is still unnerving. It really feels to me like this is an OpenFlowPlugin glitch, though.
EDIT 4: Steps for reproducing the bug
Install a rule with ipv4 arbitrary bitmask match, with the destination ip set, and the destination arbitrary bitmask either null or set to 255.255.255.255.
Ipv4MatchArbitraryBitMaskBuilder ipv4MatchBuilder = new Ipv4MatchArbitraryBitMaskBuilder();
ipv4MatchBuilder.setIpv4DestinationAddressNoMask(new Ipv4Address("10.0.0.1"));
ipv4MatchBuilder.setIpv4DestinationArbitraryBitmask(new DottedQuad("255.255.255.255"));
matchBuilder = new MatchBuilder().setEthernetMatch(ethernetMatchBuilder.build()).setLayer3Match(ipv4MatchBuilder.build());
... and so on ...
Extra optional steps: Install one such rule for the destination, one such rule for the source, and install equivalent rules where the bitmask is set to something else, like 127.255.255.255.
Make a query to MDSal to fetch flow information from the node on which you installed the flow rule.
Now, do "log:display" inside your ODL controller. You should have a warning about a malformed destination address. Additionally, the Table object you queried should contain no flows, so tableObject.getFlow() should return an empty list.
In order to try and understand git revert, I made a series of 4 simple commits -- A, B, C, D -- to a text file, foo.txt, with the intention of undoing only commit B later and leaving commits A, C, D intact.
So, in each commit, I added a new line each to the file, emulating either a feature added or a bug introduced.
After commit A, contents of foo.txt:
Feature A
After commit B, contents of foo.txt: (Here, I introduce a bug that I'll later try to undo/revert.)
Feature A
Bug
After commit C, contents of foo.txt:
Feature A
Bug
Feature C
After commit D, contents of foo.txt:
Feature A
Bug
Feature C
Feature D
Now, to undo the effects of Commit B (which introduced the bug), I did:
git revert master^^
What I expected to happen was, a new Commit E that removed the Bug line from the file, leaving the file contents as:
Feature A
Feature C
Feature D
However, I got the error:
error: could not revert bb58ed3... Bug introduced
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
with the contents of the file following the unsuccessful git revert being:
Feature A
<<<<<<< HEAD
Bug
Feature C
Feature D
=======
>>>>>>> parent of bb58ed3... Bug introduced
( bb58ed3 is the hash of Commit B, and 'Bug introduced' this commit's comment.)
Question:
What is going on here?
If even such a simple, one-line commit cannot be reverted/undone automatically, and must require manual resolution from me, then how could I revert a much more complex commit whose original developer may even be someone else!
Is there a special set of cases where git revert would be better applicable?
git sees each commit as a changelist (I simplify things here) and tries to "unapply" that when you call git revert. Each changelist also includes some context to ensure that the change makes sense. For example, if the change we want to make is "add return after line 10", it's more likely to break things than "add return after line 10, if lines 7-9 contain X, Y, and Z". So, we can describe your second commit as (again, simplifying this a little here):
Assuming that the first line of the file is Feature A.
Assuming that there is no second line.
Make the second line contain Bug.
After you've added few more lines, context of the Bug changed significantly, so git revert is not sure whether it can simply remove the line. Maybe the newly added lines actually fixed the bug. So it asks you to explicitly resolve the conflict of contexts.
As for your questions 2-3: yes, git revert is usable in cases when you're reverting a piece of file which was not changed since then. For example, the bug was introduced in a foo function, but only bar function (which is located ten lines below) was modified since then. In that case, git revert is likely to automatically revert the change, because it sees that the context is unchanged.
UPD: here is an example of why context matters even if you're trying to revert your own code:
Commit A (mind the mistype):
int some_vlue = 0;
read_int_into(some_vlue);
some_vlue = some_vlue++;
Commit B (bug introduced):
int some_vlue = 0;
some_vlue = 123;
some_vlue = some_vlue++;
Commit C (name fixed):
int some_value = 0;
some_value = 123;
some_value = some_value++;
Now, in order to revert commit B, one have to have some context, as we cannot simply replace some_value = 123 with older line read_int_into(some_vlue) - it would be compilation error.
I've copied Vu's Script, (and of course renamed it) which have access to another DB, and when I run it I have in the output the old transaction name of the old script.
here is the old transaction name which are to seen in the output : MDM_GetAssociations
Here is the renamed transaction:MDM_GET_ASSOCIATIONS_Otmann
After renaming the transaction, I run the script, I got 2 errors:
1)
Error 14 undeclared identifier `MDM_GET_ASSOCIATIONS_Otmann' Action.c C:\GCDM_Test\Scripts\MDM\MDM_Get_POSTGRE_Otmann MDM_Get_POSTGRE_Otmann
2)
Error 15 type error in argument 1 to web_custom_request'; foundint' expected `pointer to const char' Action.c C:\GCDM_Test\Scripts\MDM\MDM_Get_POSTGRE_Otmann MDM_Get_POSTGRE_Otmann
and this is my script :
//########## start the test scenario ############
web_set_max_html_param_len("8000");
web_set_sockets_option("SSL_VERSION", "TLS");
web_add_auto_header("Content-Type","application/xml");
web_add_auto_header("Accept","application/json");
web_add_auto_header("Authorization",lr_eval_string("{AUTHORIZATION}"));
//GetAssociations, NOTE: our dummy customers have often NO associations!
web_reg_save_param("RESPONSE", "LB=", "RB=", "Search=Body", LAST);
lr_start_transaction((char*)MDM_GENERIC_TRANSACTION);
lr_start_transaction((char*)MDM_GET_ASSOCIATIONS);
web_custom_request(MDM_GET_ASSOCIATIONS,
"URL={TEST_ENV_HOSTNAME}/api/v3/clients/{BUSINESS_CONTEXT}/customers/{GCID}/associations",
"Method=GET",
"Resource=1", // => We are retrieving a ressource,
// which implies that it is not critical for the success of the script.
// Any failures (HTTP 404 - Not found etc.) in downloading the resource
// will be considered as warnings rather than errors.
"EncType=application/xml",
"Referer=Loadrunner",
LAST);
lr_end_transaction((char*)MDM_GET_ASSOCIATIONS, LR_AUTO);
lr_end_transaction((char*)MDM_GENERIC_TRANSACTION, LR_AUTO);
return 0;
}
and this is the output where the old transaction name apeared (MDM_GetAssociations), but I don't know where is she coded or from where she came, and as I said before when I try to change it in all position which has to do with Transactions,I got the errors mentioned above.
Here ist the output of the script, where you can see the name of the old the transaction(MDM_GetAssociations).
Action.c(13): Notify: Transaction "MDM_GenericServiceCall_ALL" started.
Action.c(14): Notify: Transaction "MDM_GetAssociations" started.
Action.c(15): web_custom_request("MDM_GetAssociations") started
Action.c(15): web_custom_request("MDM_GetAssociations") highest severity level was "warning", 505 body bytes, 1971 header bytes [MsgId: MMSG-26388]
Action.c(25): Notify: Transaction "MDM_GetAssociations" ended with "Pass" status (Duration: 1,8408 Wasted Time: 1,2668).
Action.c(26): Notify: Transaction "MDM_GenericServiceCall_ALL" ended with "Pass" status (Duration: 2,4066 Wasted Time: 1,2668).
Ending action Action.
Ending iteration 1.
You have two variables. You do not have their declarations here. You do not have their contents. And you appear to be casting them from another data type to a pointer to a character.
Does this pass with a literal, "My_Test_Transaction"? If so, then you are likely looking at oddities on how your variable is declared, populated and referenced.
I've been asked to try to roll back some database changes if there was an error.
Before I even start trying to use a TRANSACTION with either COMMIT or ROLLBACK, could someone tell me if I can do the following in MS Access?
void Start() {
try {
AccessDatabaseOpen(); // Opens the access database
foreach (File in FileList) {
AccessTransactionStart(); // Starts the Transaction
AccessWriteSectionDataFromFile();
AccessWriteEmployeeDataFromFile();
AccessWriteSomethingElseFromFile();
} // go to next File in FileList
AccessTransactionCommit();
} catch {
AccessTransactionRollback();
} finally {
AccessDatabaseClose();
}
}
The syntax is crappy, but you should get the point: Can a routine in code start a transaction, call several other routines, and either commit or rollback the whole thing or is this idea make believe?
Thanks,
Joe
Can a routine in code start a
transaction, call several other
routines, and either commit or
rollback the whole thing
Yes, this is the basic idea of transaction handling and your outlined example would be a standard approach to deal with them from code. Details will vary depending on particular situation/needs and of course the database system used (e.g. nested transactions, scope, concurrency handling, etc.).
If a database abstraction layer is involved, check for specifics of that, as they often come with some implicit transaction handling that can often be configured by some settings/parameters.