chiark / gitweb /
Fix __checkout_files() in gitmergeonefile.py
[stgit] / gitmergeonefile.py
1 #!/usr/bin/env python
2 """Performs a 3-way merge for GIT files
3 """
4
5 __copyright__ = """
6 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as
10 published by the Free Software Foundation.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 """
21
22 import sys, os
23 from stgit.config import config
24 from stgit.utils import append_string
25
26
27 #
28 # Options
29 #
30 try:
31     merger = config.get('gitmergeonefile', 'merger')
32 except Exception, err:
33     print >> sys.stderr, 'Configuration error: %s' % err
34     sys.exit(1)
35
36 if config.has_option('gitmergeonefile', 'keeporig'):
37     keeporig = config.get('gitmergeonefile', 'keeporig')
38 else:
39     keeporig = 'yes'
40
41
42 #
43 # Global variables
44 #
45 if 'GIT_DIR' in os.environ:
46     base_dir = os.environ['GIT_DIR']
47 else:
48     base_dir = '.git'
49
50
51 #
52 # Utility functions
53 #
54 def __str2none(x):
55     if x == '':
56         return None
57     else:
58         return x
59
60 def __output(cmd):
61     f = os.popen(cmd, 'r')
62     string = f.readline().strip()
63     if f.close():
64         print >> sys.stderr, 'Error: failed to execute "%s"' % cmd
65         sys.exit(1)
66     return string
67
68 def __checkout_files():
69     """Check out the files passed as arguments
70     """
71     global orig, src1, src2
72
73     if orig_hash:
74         orig = '%s.older' % path
75         tmp = __output('git-unpack-file %s' % orig_hash)
76         os.chmod(tmp, int(orig_mode, 8))
77         os.renames(tmp, orig)
78     if file1_hash:
79         src1 = '%s.local' % path
80         tmp = __output('git-unpack-file %s' % file1_hash)
81         os.chmod(tmp, int(file1_mode, 8))
82         os.renames(tmp, src1)
83     if file2_hash:
84         src2 = '%s.remote' % path
85         tmp = __output('git-unpack-file %s' % file2_hash)
86         os.chmod(tmp, int(file2_mode, 8))
87         os.renames(tmp, src2)
88
89 def __remove_files():
90     """Remove any temporary files
91     """
92     if orig_hash:
93         os.remove(orig)
94     if file1_hash:
95         os.remove(src1)
96     if file2_hash:
97         os.remove(src2)
98     pass
99
100 def __conflict():
101     """Write the conflict file for the 'path' variable and exit
102     """
103     append_string(os.path.join(base_dir, 'conflicts'), path)
104     sys.exit(1)
105
106
107 #   $1 - original file SHA1 (or empty)
108 #   $2 - file in branch1 SHA1 (or empty)
109 #   $3 - file in branch2 SHA1 (or empty)
110 #   $4 - pathname in repository
111 #   $5 - orignal file mode (or empty)
112 #   $6 - file in branch1 mode (or empty)
113 #   $7 - file in branch2 mode (or empty)
114 #
115 #print 'gitmergeonefile.py "%s" "%s" "%s" "%s" "%s" "%s" "%s"' \
116 #      % tuple(sys.argv[1:8])
117 orig_hash, file1_hash, file2_hash, path, orig_mode, file1_mode, file2_mode = \
118            [__str2none(x) for x in sys.argv[1:8]]
119
120
121 #
122 # Main algorithm
123 #
124 __checkout_files()
125
126 # file exists in origin
127 if orig_hash:
128     # modified in both
129     if file1_hash and file2_hash:
130         # if modes are the same (git-read-tree probably dealed with it)
131         if file1_hash == file2_hash:
132             if os.system('git-update-cache --cacheinfo %s %s %s'
133                          % (file1_mode, file1_hash, path)) != 0:
134                 print >> sys.stderr, 'Error: git-update-cache failed'
135                 __conflict()
136             if os.system('git-checkout-cache -u -f -- %s' % path):
137                 print >> sys.stderr, 'Error: git-checkout-cache failed'
138                 __conflict()
139             if file1_mode != file2_mode:
140                 print >> sys.stderr, \
141                       'Error: File added in both, permissions conflict'
142                 __conflict()
143         # 3-way merge
144         else:
145             merge_ok = os.system(merger % {'branch1': src1,
146                                            'ancestor': orig,
147                                            'branch2': src2,
148                                            'output': path }) == 0
149
150             if merge_ok:
151                 os.system('git-update-cache -- %s' % path)
152                 __remove_files()
153                 sys.exit(0)
154             else:
155                 print >> sys.stderr, \
156                       'Error: three-way merge tool failed for file "%s"' % path
157                 # reset the cache to the first branch
158                 os.system('git-update-cache --cacheinfo %s %s %s'
159                           % (file1_mode, file1_hash, path))
160                 if keeporig != 'yes':
161                     __remove_files()
162                 __conflict()
163     # file deleted in both or deleted in one and unchanged in the other
164     elif not (file1_hash or file2_hash) \
165            or file1_hash == orig_hash or file2_hash == orig_hash:
166         if os.path.exists(path):
167             os.remove(path)
168         __remove_files()
169         sys.exit(os.system('git-update-cache --remove -- %s' % path))
170 # file does not exist in origin
171 else:
172     # file added in both
173     if file1_hash and file2_hash:
174         # files are the same
175         if file1_hash == file2_hash:
176             if os.system('git-update-cache --add --cacheinfo %s %s %s'
177                          % (file1_mode, file1_hash, path)) != 0:
178                 print >> sys.stderr, 'Error: git-update-cache failed'
179                 __conflict()
180             if os.system('git-checkout-cache -u -f -- %s' % path):
181                 print >> sys.stderr, 'Error: git-checkout-cache failed'
182                 __conflict()
183             if file1_mode != file2_mode:
184                 print >> sys.stderr, \
185                       'Error: File "s" added in both, permissions conflict' \
186                       % path
187                 __conflict()
188         # files are different
189         else:
190             print >> sys.stderr, \
191                   'Error: File "%s" added in branches but different' % path
192             __conflict()
193     # file added in one
194     elif file1_hash or file2_hash:
195         if file1_hash:
196             mode = file1_mode
197             obj = file1_hash
198         else:
199             mode = file2_mode
200             obj = file2_hash
201         if os.system('git-update-cache --add --cacheinfo %s %s %s'
202                      % (mode, obj, path)) != 0:
203             print >> sys.stderr, 'Error: git-update-cache failed'
204             __conflict()
205         __remove_files()
206         sys.exit(os.system('git-checkout-cache -u -f -- %s' % path))
207
208 # Un-handled case
209 print >> sys.stderr, 'Error: Un-handled merge conflict'
210 print >> sys.stderr, 'gitmergeonefile.py "%s" "%s" "%s" "%s" "%s" "%s" "%s"' \
211       % tuple(sys.argv[1:8])
212 __conflict()