Home > Blockchain >  GIT how to pull changes from a branch in one repository into a branch of another repository
GIT how to pull changes from a branch in one repository into a branch of another repository

Time:12-14

I understand this may sound silly to some, but I made a code change in a branch and committed those changes to the branch. This code change is in a specific repository of a .Net solution, and this solution is shared with many other .Net solutions.

What I am trying to do with git submodule is pull down the code change from this repository into another branch of another repository that I am working on for another code change.

Is there a way where I can pull down specifically that branch?

CodePudding user response:

Found my notes, can't post this well in a comment because of markup, the essential lines are these:

git remote add local-origin /c/git/OTHER_REPOSITORY
git pull --rebase local-origin main --allow-unrelated-histories

CodePudding user response:

You'll probably want git cherry-pick. Using it is not particularly difficult; it's the setup here that's tricky. You probably do not want git merge, with or without --allow-unrelated-histories.

Background: commits

It's important to realize that Git isn't about branches, and in some sense isn't even about files. Instead, Git is all about commits—which in turn means you need to know a lot about commits: what one is, what one does for you, and how commits relate to other commits.

Each commit in any Git repository is numbered, with a big ugly random-looking hash ID (formally, an object ID or OID) like c48035d29b4e524aed3a32f0403676f0d9128863. This ID is, in effect, the True Name (see also TV Tropes) of the commit: any Git repository that has this particular commit stores it under this UUID.1

Each commit stores some metadata, including stuff like the name and email address of the author and some date-and-time-stamps. The metadata also include a list of previous (or parent) commit hash IDs—just the raw numbers, but since those are the True Names of those commits, that lets Git find those commits. And—indirectly—each commit stores a full snapshot of every source file as of the form it had at the time the author of the commit made that commit.

The upshot of this is that, given just the hash ID, Git can get:

  • the full snapshot of this commit, if we have it; and
  • the full snapshot of this commit's parent commit, under similar conditions.

It's by comparing the two snapshots that Git figures out "what changed". A commit stores a snapshot, not a set of changes. It takes two commits to "see" any changes.


1The OID of any Git object is currently simply the SHA-1 of the object's data. Once Git supports SHA-256 more widely, commits will have two "true names", which will be awkward. It's not really clear how this will be handled in the end.


What this means for you

What I am trying to do ... is pull down the code change from this repository into another branch of another repository

Two repositories can literally share commits: if repository A has commit c48035d29b4e524aed3a32f0403676f0d9128863—let's call this "commit C1" for short—and independent repository B also has commit C1, then by the law of the True Name, this is the same commit. And—at least normally, in the absence of a shallow clone—if you have some commit, you have the commit's parent(s), and the parent(s)' parents, and so on, all the way back in time to the very first commit.

So if you want to copy the changes of some commit C2, and you have commit C2, you can have Git turn commit C2 into "what changed in commit C2". The git show command does this and shows what changed, prefixed with general information about the commit from C2's metadata. The git diff command can be used to get the difference in snapshots: you'd diff C2 against its (first and presumably only) parent. Other Git commands can also do this same trick.

Once you have the difference between C2 and its parent, you can then apply this same difference to some third commit C3. You could do this manually, which tends to be pretty tedious. Or you can have Git do it for you, with git cherry-pick.

The cherry-pick command works pretty simply: you check out the commit you want to use as your starting point, usually using git switch or git checkout:

git switch somebranch

You then give git cherry-pick the raw hash ID of the commit whose effect you want copied. Or, if you have a branch or tag name that, when resolved to a raw hash ID, gets the right commit's raw hash ID, you can use that name, but let's just use a raw hash ID here:

git cherry-pick c48035d29b4e524aed3a32f0403676f0d9128863

Git now finds the commit you specified here (I picked C1). It must exist in your current repository. Git uses that commit's metadata to find that commit's parent: there must be just a single parent, making this an ordinary commit.2 Git compares the snapshot of the commit (C1 in this case) to its parent's snapshot and figures out what changed, then figures out how to combine those changes into the current commit.

This combining is technically a merge operation—"merge as a verb", I like to call it—which means you can get merge conflicts. If you do get merge conflicts, you resolve them in the usual way. Once you're done, you use git cherry-pick --continue or git commit to finish. If there are no merge conflicts, cherry-pick goes ahead and runs git commit to finish things off for you. The final result is an ordinary commit, not a merge, and Git by default re-uses the commit message from the commit you have now copied.

The new commit is different—it gets its own new UUID, which incorporates stuff like your name and your email address and the current date and time to help ensure its uniqueness—but it "does the same thing" (has the same diff, more or less) as the commit you just copied.


2You can cherry-pick a root commit—one that has no parent—and Git will call every file "newly added". If you have some reason to cherry-pick a merge commit, which is a commit with more than one parent, you can tell git cherry-pick which parent to pretend is the only parent, so that it can get an appropriate diff. But in general you'll only cherry-pick an "ordinary" (single-parent) commit anyway.


The hitch

from this repository into ... another repository

If the two repositories are not clones of each other, or weren't in the past, it's very likely that not only is your new commit not in both repositories (you only added it to one) but also its parent commit is not in both repositories (it wasn't there to begin with).

For git cherry-pick, this is a show-stopper (meaning 3). You must get both the commit you want copied and its parent into your own repository, in order to use git cherry-pick.

Fortunately, that's trivial: just use git remote add to add, to your existing clone, the repository that does have the two commits you care about. Then run git fetch with that remote. Suppose, for instance, that the commit you just made is in $HOME/work/project23/ and you want to cherry-pick into a repository in $HOME/work/project45/. So you're now in the second repository (project45):

git remote add project23 $HOME/work/project23
git fetch project23

You now have all the commits from project23 in your project45 repository. This does bloat up the repository somewhat, but there's no need to be more picky as long as you have lots of disk space. You can now run your git cherry-pick command.

When you're done and want to clean up, run:

git remote remove project23

which will remove the name project23 and the reference to the other Git repository. Git will retain all the copied commits for some time, but will eventually purge them and your repository will shrink, and git push won't copy all of those commits elsewhere: they just sit around occupying space in that one particular clone until Git gets around to cleaning up.

(If you want to force Git to clean up early, you can run git gc --prune=now, but unless you're severely short on disk space, I do not recommend this.)

  •  Tags:  
  • git
  • Related