]> git.ipfire.org Git - thirdparty/rsync.git/blame - packaging/branch-from-patch
Use /usr/bin/env for increased portability.
[thirdparty/rsync.git] / packaging / branch-from-patch
CommitLineData
27e88dec 1#!/usr/bin/env python3 -B
2d0c7adb
WD
2
3# This script turns one or more diff files in the patches dir (which is
4# expected to be a checkout of the rsync-patches git repo) into a branch
5# in the main rsync git checkout. This allows the applied patch to be
6# merged with the latest rsync changes and tested. To update the diff
7# with the resulting changes, see the patch-update script.
8
9import os, sys, re, argparse, glob
10
11sys.path = ['packaging'] + sys.path
12
13from pkglib import *
14
15def main():
16 global created, info, local_branch
17
18 cur_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
19
20 local_branch = get_patch_branches(args.base_branch)
21
22 if args.delete_local_branches:
23 for name in sorted(local_branch):
24 branch = f"patch/{args.base_branch}/{name}"
25 cmd_chk(['git', 'branch', '-D', branch])
26 local_branch = set()
27
28 if args.add_missing:
29 for fn in sorted(glob.glob(f"{args.patches_dir}/*.diff")):
30 name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
31 if name not in local_branch and fn not in args.patch_files:
32 args.patch_files.append(fn)
33
34 if not args.patch_files:
35 return
36
37 for fn in args.patch_files:
38 if not fn.endswith('.diff'):
39 die(f"Filename is not a .diff file: {fn}")
40 if not os.path.isfile(fn):
41 die(f"File not found: {fn}")
42
43 scanned = set()
44 info = { }
45
46 patch_list = [ ]
47 for fn in args.patch_files:
48 m = re.match(r'^(?P<dir>.*?)(?P<name>[^/]+)\.diff$', fn)
49 patch = argparse.Namespace(**m.groupdict())
50 if patch.name in scanned:
51 continue
52 patch.fn = fn
53
54 lines = [ ]
55 commit_hash = None
56 with open(patch.fn, 'r', encoding='utf-8') as fh:
57 for line in fh:
58 m = re.match(r'^based-on: (\S+)', line)
59 if m:
60 commit_hash = m[1]
61 break
62 if (re.match(r'^index .*\.\..* \d', line)
63 or re.match(r'^diff --git ', line)
64 or re.match(r'^--- (old|a)/', line)):
65 break
364d302b 66 lines.append(re.sub(r'\s*\Z', "\n", line, 1))
2d0c7adb
WD
67 info_txt = ''.join(lines).strip() + "\n"
68 lines = None
69
70 parent = args.base_branch
71 patches = re.findall(r'patch -p1 <%s/(\S+)\.diff' % args.patches_dir, info_txt)
72 if patches:
73 last = patches.pop()
74 if last != patch.name:
75 warn(f"No identity patch line in {patch.fn}")
76 patches.append(last)
77 if patches:
78 parent = patches.pop()
79 if parent not in scanned:
80 diff_fn = patch.dir + parent + '.diff'
81 if not os.path.isfile(diff_fn):
82 die(f"Failed to find parent of {patch.fn}: {parent}")
83 # Add parent to args.patch_files so that we will look for the
84 # parent's parent. Any duplicates will be ignored.
85 args.patch_files.append(diff_fn)
86 else:
87 warn(f"No patch lines found in {patch.fn}")
88
89 info[patch.name] = [ parent, info_txt, commit_hash ]
90
91 patch_list.append(patch)
92
93 created = set()
94 for patch in patch_list:
95 create_branch(patch)
96
97 cmd_chk(['git', 'checkout', args.base_branch])
98
99
100def create_branch(patch):
101 if patch.name in created:
102 return
103 created.add(patch.name)
104
105 parent, info_txt, commit_hash = info[patch.name]
106 parent = argparse.Namespace(dir=patch.dir, name=parent, fn=patch.dir + parent + '.diff')
107
108 if parent.name == args.base_branch:
109 parent_branch = commit_hash if commit_hash else args.base_branch
110 else:
111 create_branch(parent)
112 parent_branch = '/'.join(['patch', args.base_branch, parent.name])
113
114 branch = '/'.join(['patch', args.base_branch, patch.name])
115 print("\n" + '=' * 64)
116 print(f"Processing {branch} ({parent_branch})")
117
118 if patch.name in local_branch:
119 cmd_chk(['git', 'branch', '-D', branch])
120
121 cmd_chk(['git', 'checkout', '-b', branch, parent_branch])
122
123 info_fn = 'PATCH.' + patch.name
124 with open(info_fn, 'w', encoding='utf-8') as fh:
125 fh.write(info_txt)
126 cmd_chk(['git', 'add', info_fn])
127
128 with open(patch.fn, 'r', encoding='utf-8') as fh:
129 patch_txt = fh.read()
130
131 cmd_run('patch -p1'.split(), input=patch_txt)
132
133 for fn in glob.glob('*.orig') + glob.glob('*/*.orig'):
134 os.unlink(fn)
135
136 pos = 0
137 new_file_re = re.compile(r'\nnew file mode (?P<mode>\d+)\s+--- /dev/null\s+\+\+\+ b/(?P<fn>.+)')
138 while True:
139 m = new_file_re.search(patch_txt, pos)
140 if not m:
141 break
142 os.chmod(m['fn'], int(m['mode'], 8))
143 cmd_chk(['git', 'add', m['fn']])
144 pos = m.end()
145
146 while True:
147 cmd_chk('git status'.split())
148 ans = input('Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: ')
149 if ans == '':
150 break
151 cmd_chk("git add " + ans, shell=True)
152
153 while True:
154 s = cmd_run(['git', 'commit', '-a', '-m', f"Creating branch from {patch.name}.diff."])
155 if not s.returncode:
156 break
157 s = cmd_run(['/bin/zsh'])
158 if s.returncode:
159 die('Aborting due to shell error code')
160
161
162if __name__ == '__main__':
163 parser = argparse.ArgumentParser(description="Create a git patch branch from an rsync patch file.", add_help=False)
164 parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
165 parser.add_argument('--add-missing', '-a', action='store_true', help="Add a branch for every patches/*.diff that doesn't have a branch.")
166 parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
167 parser.add_argument('--delete', dest='delete_local_branches', action='store_true', help="Delete all the local patch/BASE/* branches, not just the ones that are being recreated.")
168 parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
169 parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
170 parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
171 args = parser.parse_args()
172 main()
173
174# vim: sw=4 et