OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 """git drover: A tool for merging changes to release branches.""" | 5 """git drover: A tool for merging changes to release branches.""" |
6 | 6 |
7 import argparse | 7 import argparse |
8 import functools | 8 import functools |
9 import logging | 9 import logging |
10 import os | 10 import os |
11 import shutil | 11 import shutil |
12 import subprocess | 12 import subprocess |
13 import sys | 13 import sys |
14 import tempfile | 14 import tempfile |
15 | 15 |
16 import git_common | 16 import git_common |
17 | 17 |
18 | 18 |
19 class Error(Exception): | 19 class Error(Exception): |
20 pass | 20 pass |
21 | 21 |
22 | 22 |
23 if os.name == 'nt': | |
24 # This is a just-good-enough emulation of os.symlink for drover to work on | |
25 # Windows. It uses junctioning of directories (most of the contents of | |
26 # the .git directory), but copies files. Note that we can't use | |
27 # CreateSymbolicLink or CreateHardLink here, as they both require elevation. | |
28 # Creating reparse points is what we want for the directories, but doing so | |
29 # is a relatively messy set of DeviceIoControl work at the API level, so we | |
30 # simply shell to `mklink /j` instead. | |
31 def emulate_symlink_windows(source, link_name): | |
32 if os.path.isdir(source): | |
33 subprocess.check_call(['mklink', '/j', | |
34 link_name.replace('/', '\\'), | |
35 source.replace('/', '\\')], | |
36 shell=True) | |
37 else: | |
38 shutil.copy(source, link_name) | |
39 os.symlink = emulate_symlink_windows | |
iannucci
2015/10/07 22:58:26
Though I'm not totally sold on adding this back in
scottmg
2015/10/07 23:03:52
It sort of has to be os.symlink, or else various c
| |
40 | |
41 | |
23 class _Drover(object): | 42 class _Drover(object): |
24 | 43 |
25 def __init__(self, branch, revision, parent_repo, dry_run): | 44 def __init__(self, branch, revision, parent_repo, dry_run): |
26 self._branch = branch | 45 self._branch = branch |
27 self._branch_ref = 'refs/remotes/branch-heads/%s' % branch | 46 self._branch_ref = 'refs/remotes/branch-heads/%s' % branch |
28 self._revision = revision | 47 self._revision = revision |
29 self._parent_repo = os.path.abspath(parent_repo) | 48 self._parent_repo = os.path.abspath(parent_repo) |
30 self._dry_run = dry_run | 49 self._dry_run = dry_run |
31 self._workdir = None | 50 self._workdir = None |
32 self._branch_name = None | 51 self._branch_name = None |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
65 def _cleanup(self): | 84 def _cleanup(self): |
66 if self._branch_name: | 85 if self._branch_name: |
67 try: | 86 try: |
68 self._run_git_command(['cherry-pick', '--abort']) | 87 self._run_git_command(['cherry-pick', '--abort']) |
69 except Error: | 88 except Error: |
70 pass | 89 pass |
71 self._run_git_command(['checkout', '--detach']) | 90 self._run_git_command(['checkout', '--detach']) |
72 self._run_git_command(['branch', '-D', self._branch_name]) | 91 self._run_git_command(['branch', '-D', self._branch_name]) |
73 if self._workdir: | 92 if self._workdir: |
74 logging.debug('Deleting %s', self._workdir) | 93 logging.debug('Deleting %s', self._workdir) |
75 shutil.rmtree(self._workdir) | 94 if os.name == 'nt': |
95 # Use rmdir to properly handle the junctions we created. | |
96 subprocess.check_call(['rmdir', '/s', '/q', self._workdir], shell=True) | |
97 else: | |
98 shutil.rmtree(self._workdir) | |
76 self._dev_null_file.close() | 99 self._dev_null_file.close() |
77 | 100 |
78 @staticmethod | 101 @staticmethod |
79 def _confirm(message): | 102 def _confirm(message): |
80 """Show a confirmation prompt with the given message. | 103 """Show a confirmation prompt with the given message. |
81 | 104 |
82 Returns: | 105 Returns: |
83 A bool representing whether the user wishes to continue. | 106 A bool representing whether the user wishes to continue. |
84 """ | 107 """ |
85 result = '' | 108 result = '' |
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
245 try: | 268 try: |
246 cherry_pick_change(options.branch, options.cherry_pick, | 269 cherry_pick_change(options.branch, options.cherry_pick, |
247 options.parent_checkout, options.dry_run) | 270 options.parent_checkout, options.dry_run) |
248 except Error as e: | 271 except Error as e: |
249 logging.error(e.message) | 272 logging.error(e.message) |
250 sys.exit(128) | 273 sys.exit(128) |
251 | 274 |
252 | 275 |
253 if __name__ == '__main__': | 276 if __name__ == '__main__': |
254 main() | 277 main() |
OLD | NEW |