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 | ||
