diff options
Diffstat (limited to 'subcmds/rebase.py')
-rw-r--r-- | subcmds/rebase.py | 313 |
1 files changed, 180 insertions, 133 deletions
diff --git a/subcmds/rebase.py b/subcmds/rebase.py index 3d1a63e4..dc4f5805 100644 --- a/subcmds/rebase.py +++ b/subcmds/rebase.py | |||
@@ -20,146 +20,193 @@ from git_command import GitCommand | |||
20 | 20 | ||
21 | 21 | ||
22 | class RebaseColoring(Coloring): | 22 | class RebaseColoring(Coloring): |
23 | def __init__(self, config): | 23 | def __init__(self, config): |
24 | Coloring.__init__(self, config, 'rebase') | 24 | Coloring.__init__(self, config, "rebase") |
25 | self.project = self.printer('project', attr='bold') | 25 | self.project = self.printer("project", attr="bold") |
26 | self.fail = self.printer('fail', fg='red') | 26 | self.fail = self.printer("fail", fg="red") |
27 | 27 | ||
28 | 28 | ||
29 | class Rebase(Command): | 29 | class Rebase(Command): |
30 | COMMON = True | 30 | COMMON = True |
31 | helpSummary = "Rebase local branches on upstream branch" | 31 | helpSummary = "Rebase local branches on upstream branch" |
32 | helpUsage = """ | 32 | helpUsage = """ |
33 | %prog {[<project>...] | -i <project>...} | 33 | %prog {[<project>...] | -i <project>...} |
34 | """ | 34 | """ |
35 | helpDescription = """ | 35 | helpDescription = """ |
36 | '%prog' uses git rebase to move local changes in the current topic branch to | 36 | '%prog' uses git rebase to move local changes in the current topic branch to |
37 | the HEAD of the upstream history, useful when you have made commits in a topic | 37 | the HEAD of the upstream history, useful when you have made commits in a topic |
38 | branch but need to incorporate new upstream changes "underneath" them. | 38 | branch but need to incorporate new upstream changes "underneath" them. |
39 | """ | 39 | """ |
40 | 40 | ||
41 | def _Options(self, p): | 41 | def _Options(self, p): |
42 | g = p.get_option_group('--quiet') | 42 | g = p.get_option_group("--quiet") |
43 | g.add_option('-i', '--interactive', | 43 | g.add_option( |
44 | dest="interactive", action="store_true", | 44 | "-i", |
45 | help="interactive rebase (single project only)") | 45 | "--interactive", |
46 | 46 | dest="interactive", | |
47 | p.add_option('--fail-fast', | 47 | action="store_true", |
48 | dest='fail_fast', action='store_true', | 48 | help="interactive rebase (single project only)", |
49 | help='stop rebasing after first error is hit') | 49 | ) |
50 | p.add_option('-f', '--force-rebase', | 50 | |
51 | dest='force_rebase', action='store_true', | 51 | p.add_option( |
52 | help='pass --force-rebase to git rebase') | 52 | "--fail-fast", |
53 | p.add_option('--no-ff', | 53 | dest="fail_fast", |
54 | dest='ff', default=True, action='store_false', | 54 | action="store_true", |
55 | help='pass --no-ff to git rebase') | 55 | help="stop rebasing after first error is hit", |
56 | p.add_option('--autosquash', | 56 | ) |
57 | dest='autosquash', action='store_true', | 57 | p.add_option( |
58 | help='pass --autosquash to git rebase') | 58 | "-f", |
59 | p.add_option('--whitespace', | 59 | "--force-rebase", |
60 | dest='whitespace', action='store', metavar='WS', | 60 | dest="force_rebase", |
61 | help='pass --whitespace to git rebase') | 61 | action="store_true", |
62 | p.add_option('--auto-stash', | 62 | help="pass --force-rebase to git rebase", |
63 | dest='auto_stash', action='store_true', | 63 | ) |
64 | help='stash local modifications before starting') | 64 | p.add_option( |
65 | p.add_option('-m', '--onto-manifest', | 65 | "--no-ff", |
66 | dest='onto_manifest', action='store_true', | 66 | dest="ff", |
67 | help='rebase onto the manifest version instead of upstream ' | 67 | default=True, |
68 | 'HEAD (this helps to make sure the local tree stays ' | 68 | action="store_false", |
69 | 'consistent if you previously synced to a manifest)') | 69 | help="pass --no-ff to git rebase", |
70 | 70 | ) | |
71 | def Execute(self, opt, args): | 71 | p.add_option( |
72 | all_projects = self.GetProjects(args, all_manifests=not opt.this_manifest_only) | 72 | "--autosquash", |
73 | one_project = len(all_projects) == 1 | 73 | dest="autosquash", |
74 | 74 | action="store_true", | |
75 | if opt.interactive and not one_project: | 75 | help="pass --autosquash to git rebase", |
76 | print('error: interactive rebase not supported with multiple projects', | 76 | ) |
77 | file=sys.stderr) | 77 | p.add_option( |
78 | if len(args) == 1: | 78 | "--whitespace", |
79 | print('note: project %s is mapped to more than one path' % (args[0],), | 79 | dest="whitespace", |
80 | file=sys.stderr) | 80 | action="store", |
81 | return 1 | 81 | metavar="WS", |
82 | 82 | help="pass --whitespace to git rebase", | |
83 | # Setup the common git rebase args that we use for all projects. | 83 | ) |
84 | common_args = ['rebase'] | 84 | p.add_option( |
85 | if opt.whitespace: | 85 | "--auto-stash", |
86 | common_args.append('--whitespace=%s' % opt.whitespace) | 86 | dest="auto_stash", |
87 | if opt.quiet: | 87 | action="store_true", |
88 | common_args.append('--quiet') | 88 | help="stash local modifications before starting", |
89 | if opt.force_rebase: | 89 | ) |
90 | common_args.append('--force-rebase') | 90 | p.add_option( |
91 | if not opt.ff: | 91 | "-m", |
92 | common_args.append('--no-ff') | 92 | "--onto-manifest", |
93 | if opt.autosquash: | 93 | dest="onto_manifest", |
94 | common_args.append('--autosquash') | 94 | action="store_true", |
95 | if opt.interactive: | 95 | help="rebase onto the manifest version instead of upstream " |
96 | common_args.append('-i') | 96 | "HEAD (this helps to make sure the local tree stays " |
97 | 97 | "consistent if you previously synced to a manifest)", | |
98 | config = self.manifest.manifestProject.config | 98 | ) |
99 | out = RebaseColoring(config) | 99 | |
100 | out.redirect(sys.stdout) | 100 | def Execute(self, opt, args): |
101 | _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) | 101 | all_projects = self.GetProjects( |
102 | 102 | args, all_manifests=not opt.this_manifest_only | |
103 | ret = 0 | 103 | ) |
104 | for project in all_projects: | 104 | one_project = len(all_projects) == 1 |
105 | if ret and opt.fail_fast: | 105 | |
106 | break | 106 | if opt.interactive and not one_project: |
107 | 107 | print( | |
108 | cb = project.CurrentBranch | 108 | "error: interactive rebase not supported with multiple " |
109 | if not cb: | 109 | "projects", |
110 | if one_project: | 110 | file=sys.stderr, |
111 | print("error: project %s has a detached HEAD" % _RelPath(project), | 111 | ) |
112 | file=sys.stderr) | 112 | if len(args) == 1: |
113 | return 1 | 113 | print( |
114 | # ignore branches with detatched HEADs | 114 | "note: project %s is mapped to more than one path" |
115 | continue | 115 | % (args[0],), |
116 | 116 | file=sys.stderr, | |
117 | upbranch = project.GetBranch(cb) | 117 | ) |
118 | if not upbranch.LocalMerge: | 118 | return 1 |
119 | if one_project: | 119 | |
120 | print("error: project %s does not track any remote branches" | 120 | # Setup the common git rebase args that we use for all projects. |
121 | % _RelPath(project), file=sys.stderr) | 121 | common_args = ["rebase"] |
122 | return 1 | 122 | if opt.whitespace: |
123 | # ignore branches without remotes | 123 | common_args.append("--whitespace=%s" % opt.whitespace) |
124 | continue | 124 | if opt.quiet: |
125 | 125 | common_args.append("--quiet") | |
126 | args = common_args[:] | 126 | if opt.force_rebase: |
127 | if opt.onto_manifest: | 127 | common_args.append("--force-rebase") |
128 | args.append('--onto') | 128 | if not opt.ff: |
129 | args.append(project.revisionExpr) | 129 | common_args.append("--no-ff") |
130 | 130 | if opt.autosquash: | |
131 | args.append(upbranch.LocalMerge) | 131 | common_args.append("--autosquash") |
132 | 132 | if opt.interactive: | |
133 | out.project('project %s: rebasing %s -> %s', | 133 | common_args.append("-i") |
134 | _RelPath(project), cb, upbranch.LocalMerge) | 134 | |
135 | out.nl() | 135 | config = self.manifest.manifestProject.config |
136 | out.flush() | 136 | out = RebaseColoring(config) |
137 | 137 | out.redirect(sys.stdout) | |
138 | needs_stash = False | 138 | _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) |
139 | if opt.auto_stash: | 139 | |
140 | stash_args = ["update-index", "--refresh", "-q"] | 140 | ret = 0 |
141 | 141 | for project in all_projects: | |
142 | if GitCommand(project, stash_args).Wait() != 0: | 142 | if ret and opt.fail_fast: |
143 | needs_stash = True | 143 | break |
144 | # Dirty index, requires stash... | 144 | |
145 | stash_args = ["stash"] | 145 | cb = project.CurrentBranch |
146 | 146 | if not cb: | |
147 | if GitCommand(project, stash_args).Wait() != 0: | 147 | if one_project: |
148 | ret += 1 | 148 | print( |
149 | continue | 149 | "error: project %s has a detached HEAD" |
150 | 150 | % _RelPath(project), | |
151 | if GitCommand(project, args).Wait() != 0: | 151 | file=sys.stderr, |
152 | ret += 1 | 152 | ) |
153 | continue | 153 | return 1 |
154 | 154 | # Ignore branches with detached HEADs. | |
155 | if needs_stash: | 155 | continue |
156 | stash_args.append('pop') | 156 | |
157 | stash_args.append('--quiet') | 157 | upbranch = project.GetBranch(cb) |
158 | if GitCommand(project, stash_args).Wait() != 0: | 158 | if not upbranch.LocalMerge: |
159 | ret += 1 | 159 | if one_project: |
160 | 160 | print( | |
161 | if ret: | 161 | "error: project %s does not track any remote branches" |
162 | out.fail('%i projects had errors', ret) | 162 | % _RelPath(project), |
163 | out.nl() | 163 | file=sys.stderr, |
164 | 164 | ) | |
165 | return ret | 165 | return 1 |
166 | # Ignore branches without remotes. | ||
167 | continue | ||
168 | |||
169 | args = common_args[:] | ||
170 | if opt.onto_manifest: | ||
171 | args.append("--onto") | ||
172 | args.append(project.revisionExpr) | ||
173 | |||
174 | args.append(upbranch.LocalMerge) | ||
175 | |||
176 | out.project( | ||
177 | "project %s: rebasing %s -> %s", | ||
178 | _RelPath(project), | ||
179 | cb, | ||
180 | upbranch.LocalMerge, | ||
181 | ) | ||
182 | out.nl() | ||
183 | out.flush() | ||
184 | |||
185 | needs_stash = False | ||
186 | if opt.auto_stash: | ||
187 | stash_args = ["update-index", "--refresh", "-q"] | ||
188 | |||
189 | if GitCommand(project, stash_args).Wait() != 0: | ||
190 | needs_stash = True | ||
191 | # Dirty index, requires stash... | ||
192 | stash_args = ["stash"] | ||
193 | |||
194 | if GitCommand(project, stash_args).Wait() != 0: | ||
195 | ret += 1 | ||
196 | continue | ||
197 | |||
198 | if GitCommand(project, args).Wait() != 0: | ||
199 | ret += 1 | ||
200 | continue | ||
201 | |||
202 | if needs_stash: | ||
203 | stash_args.append("pop") | ||
204 | stash_args.append("--quiet") | ||
205 | if GitCommand(project, stash_args).Wait() != 0: | ||
206 | ret += 1 | ||
207 | |||
208 | if ret: | ||
209 | out.fail("%i projects had errors", ret) | ||
210 | out.nl() | ||
211 | |||
212 | return ret | ||