1fe226e0fcb1bb824764b84609f12a975d19ba19
[stgit] / stgit / gitmergeonefile.py
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
22 from stgit.exception import *
23 from stgit import basedir
24 from stgit.config import config, file_extensions, ConfigOption
25 from stgit.utils import append_string
26 from stgit.out import *
27 from stgit.run import *
28
29 class GitMergeException(StgException):
30 pass
31
32
33 #
34 # Options
35 #
36 autoimerge = ConfigOption('stgit', 'autoimerge')
37 keeporig = ConfigOption('stgit', 'keeporig')
38
39 #
40 # Utility functions
41 #
42 def __str2none(x):
43 if x == '':
44 return None
45 else:
46 return x
47
48 class MRun(Run):
49 exc = GitMergeException # use a custom exception class on errors
50
51 def __checkout_stages(filename):
52 """Check-out the merge stages in the index for the give file
53 """
54 extensions = file_extensions()
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
71
72 return stages
73
74 def __remove_stages(filename):
75 """Remove the merge stages from the working directory
76 """
77 extensions = file_extensions()
78 for ext in extensions.itervalues():
79 fn = filename + ext
80 if os.path.isfile(fn):
81 os.remove(fn)
82
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().
87 """
88 stages = __checkout_stages(filename)
89
90 try:
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:
125 # keep the merge stages?
126 if str(keeporig) != 'yes':
127 __remove_stages(filename)
128
129 def clean_up(filename):
130 """Remove merge conflict stages if they were generated.
131 """
132 if str(keeporig) == 'yes':
133 __remove_stages(filename)
134
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