How can I generate/apply git patches only for commits that alter specific files
Asked Answered
J

3

6

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.

Jany answered 11/4, 2013 at 21:33 Comment(0)
T
13

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)

Tonsil answered 12/4, 2013 at 0:15 Comment(7)
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?Fanniefannin
Use -E instead, -r is now an historical spelling. Fixed in the answer, thanks.Tonsil
It would be nice if you can give an explanation on your sed script !Homebody
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.Parasitize
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.Shaynashayne
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.Gelid
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 logGelid
C
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
Coan answered 11/4, 2013 at 21:44 Comment(1)
No, unfortunately they do not share a history. :(Jany
M
1

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.

Mainis answered 19/11, 2021 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.