2

I have files of a project A which have been copied into project B. In a branch of B, the code originally belonging to project A has been modified.

How can I copy the history of changes done in B's branch into the original project A?

Consider that I do not want to copy all the files of project B into A, but just the ones which already belong to project A.

Reference: How do you merge two Git repositories?

Community
  • 1
  • 1
Pietro
  • 12,086
  • 26
  • 100
  • 193
  • http://stackoverflow.com/questions/13788945/how-to-cherry-pick-from-a-remote-branch Cherry-pick the commits? – Bryce Drew May 26 '16 at 18:32

2 Answers2

2

(I put part of this in as a comment, but decided to expand on it.)

Note that in Git, files do not have history. Commits are history, and commits have files.

For instance, perhaps commit a666666 has files A, B, and C and message "modify A", and its history is "parent is a555555". Now we step back to commit a555555. It has files A, B, and C and message "add file C" and parent a444444. We then step back to commit a444444, which has files A and B, parent a333333, and some message; and so on.

It sounds like what you want to do is extract every commit that touches certain paths ("files in project B"), copy those specific file-paths over to some other repository, and then—in that other repository—make a new commit, perhaps re-using the commit message from the current commit.

There are a number of ways to do this. None is necessarily best, although some may make things easier, depending on just what results you want. None are completely built-in to Git either, so you must do at least a little bit of programming. I'll show one method, which is the most obvious to me.

You should also decide what, if anything, you want to do with merge commits. Merges cause non-linear history, and copying non-linear history requires either extreme cleverness, or some sort of simplification. I will leave details to you.

As for how to copy the commits, here are two relatively simplistic methods that assume we only care about some sort linear or linearize-able history.

Basic setup and finding interesting commits

Set up repo src, the source repository that contains commits you want to make copies from (i.e., a repo copy of project B in your question). Set up second repo dst, the destination (target) into which you will copy files from commit in src.

Now we need to find "interesting commits". These are commits that modify "interesting files", i.e., files that you wish to copy into dst.

The Git command to list commit IDs is git rev-list, which takes about four billion options to specify which Git objects are interesting and how to show them. In our case we want commits, listed in reverse topological order (oldest first), starting from the latest point you want to copy from—probably a branch name—and probably stopping at some specified point, but only those commits that modified specific path(s). Hence:

git rev-list --topo-order --reverse latest ^stopat -- path1 path2 ... pathN

(Note that you may list directory paths.) Here latest is the branch name, or a commit ID; ^stopat is the literal character ^ followed by a name or another commit ID; and the paths are the names of all the files, or directories full of files, that are "interesting".

You may want to include --first-parent and/or --no-merges as well, depending on how you want your history linearized. Study the git rev-list documentation to decide for yourself.

(Along the way, you may note that stopat..latest is a shorthand syntax for the same thing we did in longer form above.)

You may want to redirect the output of the above command to a file, since it might be quite long. You can then inspect the selected revisions, or some random subset of them, to see if you like the ones selected. (Use git show to view specific commits; note that by default, git show uses combined diffs to show merge commits.)

Now that you have a list of interesting commits, we can go on to the methods for copying (part of) those commits from src to dst. Note: none of the code below is tested.

Method: literal copy

This is probably the simplest method. Write a script that iterates through each commit ID and checks out that commit in src, then copies the files from src to dst, git adds them in dst, and runs git commit. It's OK to re-add files that are not changed, so this script is pretty simple. This assumes that ./list-of-files contains "interesting" paths and the repos are in ./src and ./dst, with the interesting commit revision IDs in ./interesting. (You must write die; it's a simple shell function.) It probably has some issues if not every path exists in every commit (not a problem if the paths are just one subdirectory). It definitely has issues if any of the path arguments have whitespace in them, since $(cat ../list-of-paths) will just split at any white space.

while read rev; do
    (cd src &&
     git checkout -q $rev &&
     git log -1 --pretty=format:%B > ../commit-msg) ||
    die "failed to check out $rev in src"

    (cd src && cp -R $(cat ../list-of-paths) ../dst/) ||
    die "failed to copy files from src to dst"

    (cd dst &&
     git add -- $(cat ../list-of-paths) &&
     git commit -q -F ../commit-msg) ||
    die "failed to commit $rev in dst"
done < interesting
torek
  • 448,244
  • 59
  • 642
  • 775
1

See 'git help git' for GIT_DIR environment variable. Then add the Project B modified files, as if you were in Project B directory. Something like the following example.

move to project B and set git to Project A, Verify the Project A git with the 'rev-parse' command

cd <ProjectB>
GIT_DIR="<ProjectA/.git>"
git rev-parse --git-dir

get list of modified files, enter the changed files manually from results of 'git status'

git diff --name-status --diff-filter=M
files="<changedFile1> <changedFile2> <...>"

put files changed in Project B to Project A's Repository

git add -- $files
git commit -m"commit files modified in Project B"

Restore git repository

GIT_DIR=
Gregg
  • 2,444
  • 1
  • 12
  • 21
  • After: `cd "path_to_B" ; GIT_DIR="path_to_A/.git" ; git rev-parse --git-dir` I get this: `path_to_B/.git` . Is it Ok? In A repository nothing changed, I still have the old version. – Pietro May 27 '16 at 15:37
  • It should say path_to_A.git, if not then reassigning the git repository did not work. If you are windows type 'set GIT_DIR= and at the end 'set GIT_DIR='. and then confirm with the git rev-parse. see also 'git help git' for the --git-dir command line option. – Gregg May 31 '16 at 12:40