5

I have two git repos that are forks of each other and I need to occasionally import commits from one to the other.

For example:

git-repo1 has this directory structure:

repo1/project1/src

repo1/project2/src

while git-repo2 has the following directory structure:

repo2/src/

What I'd like to do is take a series of commits and generate patches only for commits that altered files within a particular subdirectory (say repo1/project1/src) and ignore all commits that only alter files anywhere else.

Or alternatively, generate patches for all the commits, but only apply the patch IF it alters files within a particular directory.

I need to preserve the metadata about the commits so playing with git diff doesn't seem like a viable option.

The directory structure between the forked git repos differs.

Is there a straight forward way to do this?

UPDATE1

I see this question (How to apply a git patch from one repository to another?) in terms of coping with differing directory structures.

But what if the patch speaks of modifying files that simply do not exist? I would like to ignore such changes.

Community
  • 1
  • 1
EMiller
  • 2,792
  • 4
  • 34
  • 55

3 Answers3

12

git rev-list --reverseseries-- repo1/project1/src/ \
| xargs -I@ git format-patch --stdout @^! >mystuff.patch

will spit the commits in series that affect that subdirectory into mystuff.patch

Then,

cat >mystuff.sed <<\EOD
/^(From [0-9a-f]{40}|diff --git )/!{H;$!d}
x
/^From /b
${h;s,.*--,--,;x}
\,^diff[^\n]* [ab]/repo1/project1/src/,!{$!d;x;b}
${p;x}
EOD

and

sed -Ef mystuff.sed mystuff.patch >justmystuff.patch

will strip out all the hunks outside that directory. You can apply with

git am justmystuff.patch

with -pn and --directory=new/path/to as desired.

(edit: EOD --> \EOD so the cat above doesn't try to substitute)

jthill
  • 55,082
  • 5
  • 77
  • 137
  • When using the command "sed -rf mystuff.sed mystuff.patch >justmystuff.patch" Im getting the error "sed: illegal option -- r" Have any idea what it could be? – Mient-jan Stelling Feb 15 '15 at 00:52
  • Use `-E` instead, `-r` is now an historical spelling. Fixed in the answer, thanks. – jthill Feb 15 '15 at 01:37
  • 2
    It would be nice if you can give an explanation on your sed script ! – Neil Jun 27 '16 at 06:34
  • Search and replace with your favourite text editor also works (well, worked for me at least). I just changed the paths and removed the bits that referred to files outside the subdirectory. I didn't touch the commit hashes - and I don't know if the automagical script above does as I'm like @Neil. –  Aug 07 '17 at 09:10
  • Is it possible to make changes that allows you to also keep creations and changes in other particular files or multiple paths? For example if I have a sub-directory `sub-repo-a/` and externally to that I have a file called `.sub-repo-a.settings`. I would like to keep commits that affects ither of these paths. – Kajsa Jul 16 '20 at 11:46
  • How to teach the `mystuff.sed` magic to add to the patch commit message something like `imported from hash`? Maybe one will need to add `-k` to both `git am` and `git format-patch` to include possible [blah] in the commit messages. Also adding `--no-renames` seems to make `git am` happy if a file was moved from `repo1/project2/src` to `repo1/project1/src`. – vvassilev Jul 02 '21 at 07:30
  • https://stackoverflow.com/users/7767933/komar, a cool guy on the sed irc channel, suggested: ```# Collect patch header and body of chunks in hold buffer /^(From [0-9a-f]{40}|diff --git )/!{ # detect end of header and insert line "llvm-monorepo: HASH" from "From HASH" /^---$/{ x s/(From )([0-9a-f]{40})(.*)/\1\2\3\n\nllvm-monorepo: \2/ x } H;$!d } x # ouput "From" line from header /^From /b # if patch in '[ab]/lvm/' - output it \,^diff[^\n]* [ab]/llvm/,!{$!d;x;b} # other chunks will be skipped``` Fixes the case if there is a diff pasted in a commit log – vvassilev Jul 02 '21 at 10:15
1

if the two repositories have common history (they are both forked from the same repository, but have evolved differently), you could use cherry-picking to import commits selectively from one branch to another.

create a local repository with two remotes (your two diverging repositories)

find the commits in repositoryA that touch certain files

 $ git checkout repoA/master
 $ git log sub/dir/ectory
 a34256f ...

cherry-pick those commits into the branch of repositoryB

 git checkout repoB/master
 git cherry-pick a34256f
umläute
  • 28,885
  • 9
  • 68
  • 122
0

I tried few different methods found in here and other answers but didn't find a solution fitting for me. This is what I found out.

You can give git format-patch a path as an argument so that it'll create patch only for only those paths.

git format-patch HEAD --root $(git rev-list --max-parents=0 HEAD) --stdout -- repo1/project1/src/ > git.patch

The command git rev-list --max-parents=0 HEAD is used to fetch the initial commit ref from the repository

Applying the patch can be done with

git am git.patch

In my case I also wanted to skip one directory level when applying the patch because I was moving one whole directory from a repository to a separate repository. git apply which gets used by git am has a flag -p<n> which removes leading path components from the diff paths. The default is 1, so git am -p2 git.patch did the work for me.

Zachu
  • 41
  • 3