chiark / gitweb /
Merge branch 'stable'
[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