0c0f629a1d6898147a862b08cbd0bafc94b6da89
1#!/usr/bin/python
2#
3# p4-git-sync.py
4#
5# Author: Simon Hausmann <hausmann@kde.org>
6# License: MIT <http://www.opensource.org/licenses/mit-license.php>
7#
8
9import os, string, shelve, stat
10import getopt, sys, marshal
11
12def p4CmdList(cmd):
13 cmd = "p4 -G %s" % cmd
14 pipe = os.popen(cmd, "rb")
15
16 result = []
17 try:
18 while True:
19 entry = marshal.load(pipe)
20 result.append(entry)
21 except EOFError:
22 pass
23 pipe.close()
24
25 return result
26
27def p4Cmd(cmd):
28 list = p4CmdList(cmd)
29 result = {}
30 for entry in list:
31 result.update(entry)
32 return result;
33
34try:
35 opts, args = getopt.getopt(sys.argv[1:], "", [ "continue", "git-dir=", "origin=", "reset", "master=",
36 "submit-log-subst=", "log-substitutions=" ])
37except getopt.GetoptError:
38 print "fixme, syntax error"
39 sys.exit(1)
40
41logSubstitutions = {}
42logSubstitutions["<enter description here>"] = "%log%"
43logSubstitutions["\tDetails:"] = "\tDetails: %log%"
44gitdir = os.environ.get("GIT_DIR", "")
45origin = "origin"
46master = "master"
47firstTime = True
48reset = False
49
50for o, a in opts:
51 if o == "--git-dir":
52 gitdir = a
53 elif o == "--origin":
54 origin = a
55 elif o == "--master":
56 master = a
57 elif o == "--continue":
58 firstTime = False
59 elif o == "--reset":
60 reset = True
61 firstTime = True
62 elif o == "--submit-log-subst":
63 key = a.split("%")[0]
64 value = a.split("%")[1]
65 logSubstitutions[key] = value
66 elif o == "--log-substitutions":
67 for line in open(a, "r").readlines():
68 tokens = line[:-1].split("=")
69 logSubstitutions[tokens[0]] = tokens[1]
70
71if len(gitdir) == 0:
72 gitdir = ".git"
73else:
74 os.environ["GIT_DIR"] = gitdir
75
76configFile = gitdir + "/p4-git-sync.cfg"
77
78origin = "origin"
79if len(args) == 1:
80 origin = args[0]
81
82def die(msg):
83 sys.stderr.write(msg + "\n")
84 sys.exit(1)
85
86def system(cmd):
87 if os.system(cmd) != 0:
88 die("command failed: %s" % cmd)
89
90def check():
91 return
92 if len(p4CmdList("opened ...")) > 0:
93 die("You have files opened with perforce! Close them before starting the sync.")
94
95def start(config):
96 if len(config) > 0 and not reset:
97 die("Cannot start sync. Previous sync config found at %s" % configFile)
98
99 #if len(os.popen("git-update-index --refresh").read()) > 0:
100 # die("Your working tree is not clean. Check with git status!")
101
102 commits = []
103 for line in os.popen("git-rev-list --no-merges %s..%s" % (origin, master)).readlines():
104 commits.append(line[:-1])
105 commits.reverse()
106
107 config["commits"] = commits
108
109# print "Cleaning index..."
110# system("git checkout -f")
111
112def prepareLogMessage(template, message):
113 result = ""
114
115 substs = logSubstitutions
116 for k in substs.keys():
117 substs[k] = substs[k].replace("%log%", message)
118
119 for line in template.split("\n"):
120 if line.startswith("#"):
121 result += line + "\n"
122 continue
123
124 substituted = False
125 for key in substs.keys():
126 if line.find(key) != -1:
127 value = substs[key]
128 if value != "@remove@":
129 result += line.replace(key, value) + "\n"
130 substituted = True
131 break
132
133 if not substituted:
134 result += line + "\n"
135
136 return result
137
138def apply(id):
139 print "Applying %s" % (os.popen("git-log --max-count=1 --pretty=oneline %s" % id).read())
140 diff = os.popen("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)).readlines()
141 filesToAdd = set()
142 filesToDelete = set()
143 for line in diff:
144 modifier = line[0]
145 path = line[1:].strip()
146 if modifier == "M":
147 system("p4 edit %s" % path)
148 elif modifier == "A":
149 filesToAdd.add(path)
150 if path in filesToDelete:
151 filesToDelete.remove(path)
152 elif modifier == "D":
153 filesToDelete.add(path)
154 if path in filesToAdd:
155 filesToAdd.remove(path)
156 else:
157 die("unknown modifier %s for %s" % (modifier, path))
158
159 system("git-diff-files --name-only -z | git-update-index --remove -z --stdin")
160 system("git cherry-pick --no-commit \"%s\"" % id)
161
162 for f in filesToAdd:
163 system("p4 add %s" % f)
164 for f in filesToDelete:
165 system("p4 revert %s" % f)
166 system("p4 delete %s" % f)
167
168 logMessage = ""
169 foundTitle = False
170 for log in os.popen("git-cat-file commit %s" % id).readlines():
171 log = log[:-1]
172 if not foundTitle:
173 if len(log) == 0:
174 foundTitle = 1
175 continue
176
177 if len(logMessage) > 0:
178 logMessage += "\t"
179 logMessage += log + "\n"
180
181 template = os.popen("p4 change -o").read()
182 fileName = "submit.txt"
183 file = open(fileName, "w+")
184 file.write(prepareLogMessage(template, logMessage))
185 file.close()
186 print "Perforce submit template written as %s. Please review/edit and then use p4 submit -i < %s to submit directly!" % (fileName, fileName)
187
188check()
189
190config = shelve.open(configFile, writeback=True)
191
192if firstTime:
193 start(config)
194
195commits = config.get("commits", [])
196
197if len(commits) > 0:
198 firstTime = False
199 commit = commits[0]
200 commits = commits[1:]
201 config["commits"] = commits
202 apply(commit)
203
204config.close()
205
206if len(commits) == 0:
207 if firstTime:
208 print "No changes found to apply between %s and current HEAD" % origin
209 else:
210 print "All changes applied!"
211 os.remove(configFile)
212