Commit | Line | Data |
---|---|---|
3659ef88 CM |
1 | """Performs a 3-way merge for GIT files |
2 | """ | |
3 | ||
4 | __copyright__ = """ | |
5 | Copyright (C) 2006, Catalin Marinas <catalin.marinas@gmail.com> | |
6 | ||
7 | This program is free software; you can redistribute it and/or modify | |
8 | it under the terms of the GNU General Public License version 2 as | |
9 | published by the Free Software Foundation. | |
10 | ||
11 | This program is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU General Public License | |
17 | along with this program; if not, write to the Free Software | |
18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | """ | |
20 | ||
21 | import sys, os | |
87c93eab | 22 | from stgit.exception import * |
170f576b | 23 | from stgit import basedir |
f7ed76a9 | 24 | from stgit.config import config, file_extensions, ConfigOption |
5e888f30 KH |
25 | from stgit.utils import append_string |
26 | from stgit.out import * | |
f0de3f92 | 27 | from stgit.run import * |
3659ef88 | 28 | |
87c93eab | 29 | class GitMergeException(StgException): |
3659ef88 CM |
30 | pass |
31 | ||
32 | ||
33 | # | |
34 | # Options | |
35 | # | |
29197bc0 | 36 | autoimerge = ConfigOption('stgit', 'autoimerge') |
eee7283e | 37 | keeporig = ConfigOption('stgit', 'keeporig') |
3659ef88 CM |
38 | |
39 | # | |
40 | # Utility functions | |
41 | # | |
42 | def __str2none(x): | |
43 | if x == '': | |
44 | return None | |
45 | else: | |
46 | return x | |
47 | ||
f0de3f92 KH |
48 | class MRun(Run): |
49 | exc = GitMergeException # use a custom exception class on errors | |
3659ef88 | 50 | |
29197bc0 CM |
51 | def __checkout_stages(filename): |
52 | """Check-out the merge stages in the index for the give file | |
3659ef88 | 53 | """ |
1e075406 | 54 | extensions = file_extensions() |
29197bc0 CM |
55 | line = MRun('git', 'checkout-index', '--stage=all', '--', filename |
56 | ).output_one_line() | |
57 | stages, path = line.split('\t') | |
58 | stages = dict(zip(['ancestor', 'current', 'patched'], | |
59 | stages.split(' '))) | |
60 | ||
61 | for stage, fn in stages.iteritems(): | |
62 | if stages[stage] == '.': | |
63 | stages[stage] = None | |
64 | else: | |
65 | newname = filename + extensions[stage] | |
66 | if os.path.exists(newname): | |
67 | # remove the stage if it is already checked out | |
68 | os.remove(newname) | |
69 | os.rename(stages[stage], newname) | |
70 | stages[stage] = newname | |
1e075406 | 71 | |
29197bc0 | 72 | return stages |
8d415553 | 73 | |
29197bc0 CM |
74 | def __remove_stages(filename): |
75 | """Remove the merge stages from the working directory | |
3659ef88 | 76 | """ |
29197bc0 CM |
77 | extensions = file_extensions() |
78 | for ext in extensions.itervalues(): | |
79 | fn = filename + ext | |
80 | if os.path.isfile(fn): | |
81 | os.remove(fn) | |
3659ef88 | 82 | |
29197bc0 CM |
83 | def interactive_merge(filename): |
84 | """Run the interactive merger on the given file. Stages will be | |
85 | removed according to stgit.keeporig. If successful and stages | |
86 | kept, they will be removed via git.resolved(). | |
3659ef88 | 87 | """ |
29197bc0 | 88 | stages = __checkout_stages(filename) |
3659ef88 | 89 | |
c5bd7632 | 90 | try: |
c5bd7632 KH |
91 | # Check whether we have all the files for the merge. |
92 | if not (stages['current'] and stages['patched']): | |
93 | raise GitMergeException('Cannot run the interactive merge') | |
94 | ||
95 | if stages['ancestor']: | |
96 | three_way = True | |
97 | files_dict = {'branch1': stages['current'], | |
98 | 'ancestor': stages['ancestor'], | |
99 | 'branch2': stages['patched'], | |
100 | 'output': filename} | |
101 | imerger = config.get('stgit.i3merge') | |
102 | else: | |
103 | three_way = False | |
104 | files_dict = {'branch1': stages['current'], | |
105 | 'branch2': stages['patched'], | |
106 | 'output': filename} | |
107 | imerger = config.get('stgit.i2merge') | |
108 | ||
109 | if not imerger: | |
110 | raise GitMergeException, 'No interactive merge command configured' | |
111 | ||
112 | mtime = os.path.getmtime(filename) | |
113 | ||
114 | out.start('Trying the interactive %s merge' | |
115 | % (three_way and 'three-way' or 'two-way')) | |
116 | err = os.system(imerger % files_dict) | |
117 | out.done() | |
118 | if err != 0: | |
119 | raise GitMergeException, 'The interactive merge failed' | |
120 | if not os.path.isfile(filename): | |
121 | raise GitMergeException, 'The "%s" file is missing' % filename | |
122 | if mtime == os.path.getmtime(filename): | |
123 | raise GitMergeException, 'The "%s" file was not modified' % filename | |
124 | finally: | |
29197bc0 CM |
125 | # keep the merge stages? |
126 | if str(keeporig) != 'yes': | |
127 | __remove_stages(filename) | |
f7ed76a9 | 128 | |
29197bc0 CM |
129 | def clean_up(filename): |
130 | """Remove merge conflict stages if they were generated. | |
3659ef88 | 131 | """ |
29197bc0 CM |
132 | if str(keeporig) == 'yes': |
133 | __remove_stages(filename) | |
f7ed76a9 | 134 | |
29197bc0 CM |
135 | def merge(filename): |
136 | """Merge one file if interactive is allowed or check out the stages | |
137 | if keeporig is set. | |
138 | """ | |
139 | if str(autoimerge) == 'yes': | |
140 | try: | |
141 | interactive_merge(filename) | |
142 | except GitMergeException, ex: | |
143 | out.error(str(ex)) | |
144 | return False | |
145 | return True | |
146 | ||
147 | if str(keeporig) == 'yes': | |
148 | __checkout_stages(filename) | |
149 | ||
150 | return False |