My favorites | Sign in
Project Logo
                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#!/usr/bin/env python
#
# p4branch.py Jim Tilander 2008 http://www.tilander.org/aurora
#
# This scripts allows you to branch your current clientspec to another location
# in perforce and keep your work! Yay! It basically follows the instructions here
# http://kb.perforce.com/UserTasks/CodelinesAndBranching/BranchingWorkInProgress
# and it also uses the p4shelf.py module I wrote.
#
# This tool is released "as is" with no guarantees to function nor warranty of any
# sort. Use it at your own risk. Read more about it (including license) at
# http://www.tilander.org/aurora
#
# Seriously !!! While this script tries to be "safe", and I've labored to be correct,
# I can't possibly imagine all the whacky ways people use perforce, so this script might
# not work for you. At all. Please be very careful while doing on the fly branches, so that
# you don't loose your work. It would suck. I know, I've been there.
#
import os,sys,marshal
import logging
import p4shelf
import re
import getopt

def unhookfiles():
"""
Reverts all the files in the clientspec, but keeps the local data around unchanged
"""
for entry in p4shelf.p4('opened'):
depotFile = entry['depotFile']
logging.debug( 'reverting %s from the client' % (depotFile) )
p4shelf.p4( 'revert -k "%s"' % depotFile )

def checkClientspec(clientname):
# Check if this is a multiline clientspec, then abort!
views = 0
for entry in p4shelf.p4( 'client -o' ):
for key in entry.keys():
if key.lower().startswith('view'):
views = views + 1
return views == 1

def getClientName(clientname):
return p4shelf.p4( 'client -o', '-c %s' % clientname)[0]['Client']

def clientspaceSwitch(clientname, newClientPath):
"""
Replaces the depot portion of the clientspec with the new path given. Note that just the
very first line gets the substitution here so it really only works with single line
clientspecs.
"""
clientspec = os.popen( 'p4 -c %s client -o' % clientname, 'rt' ).read()
newclient = re.sub( '(//.+) //', '%s //' % newClientPath, clientspec )
os.popen('p4 -c %s client -i' % clientname, 'wt' ).write(newclient)

def branchCurrentView(clientname, newPath):
"""
This branches whatever you have synced to in your current view (but not any changes you have)
to another location, without actually copying any files from the server. Branching in perforce is
really a server side operation (it doesn't even copy files) so we don't need to pull down files from
the server.

It does that by cloning this clientspec and then rewriting the view to match the new location and
then submitting it.
"""
# Figure out what our base path is
view = p4shelf.p4( 'client -o' )[0]['View0']
depotPath = re.search( '(//.+)\s+//', view).group(1)

# Create a new client (or rewrite the one there) to hold the view we try to integrate into...
newclient = clientname + '_Tmp'
clientspec = os.popen( 'p4 -c %s client -o' % clientname, 'rt' ).read()
newspec = re.sub( '(//.+) //', '%s //' % newPath, clientspec )
newspec = newspec.replace( getClientName(clientname), newclient )
os.popen('p4 -c %s client -i' % newclient, 'wt' ).write(newspec)

# Now switch personality to the new client, integrate this client's files and then submit them into the new branch.
p4shelf.p4( 'integ -v %s@%s %s' % (depotPath, clientname, newPath), '-c %s' % newclient)
p4shelf.p4( 'submit -d "On the fly branching from clientspec %s"' % clientname, '-c %s' % newclient)

# Delete the temporary client when we're done
p4shelf.p4( 'client -d %s' % newclient )

def doit(clientname, newClientPath, doBranching, skipBackups, inplaceOperations):
# First check if we can actually have a chance to replace the clientspec...
if not checkClientspec(clientname):
logging.error( 'Only support single line clientspecs.' )
return 1

# First find out the clientspec's root.
rootDirectory = p4shelf.p4( "client -o" )[0]['Root']
logging.info( 'My root directory is %s' % rootDirectory )

# Check if we can backup the root directory later on (really paranoid)
if not skipBackups:
counter = 0
backupDirectory = '%s%08d.bak' % (rootDirectory, counter)
while os.path.isfile(backupDirectory) or os.path.isdir(backupDirectory):
counter = counter + 1
backupDirectory = '%s%08d.bak' % (rootDirectory, counter)

# If we've requested on the fly branching, then do it at the very beginning, since this can go wrong, and
# if it does, then we want to stop really early...
if doBranching:
try:
logging.info( 'Branching client files into new location %s' % newClientPath )
branchCurrentView(clientname, newClientPath)
except IOError, e:
logging.exception(e)
print '\n\n\n'
logging.error( 'Failed to create target branch (perhaps it already exists?), aborting...' )
return 1

# This is the temporary .zip file that we will squirrel away the current state of the workspace.
tempfilename = os.path.join( os.environ['TEMP'], 'p4migrate.zip' )

# Shelf all the work.
logging.info('Saving currently opened files on this client (going to restore them later at the new location) to %s' % tempfilename)
p4shelf.main( ['-r', '-f', '-z', '-y', '-o', '-c', clientname, tempfilename] )

if not skipBackups:
try:
# Backup this directory (just move it to another location)
logging.info('Backing up the current clientspec %s -> %s' % (rootDirectory, backupDirectory))
os.rename( rootDirectory, backupDirectory )
except WindowsError, e:
logging.error( 'There is already a backup directory %s, abort.' % backupDirectory )
return 1

# Fools perforce into thinking that we have no opened files on this client
logging.info( 'Reverting files from local client (but they are already shelved at %s' % tempfilename)
unhookfiles()

# Tell perforce that we don't have any revisions of any files on this machine.
if not inplaceOperations:
p4shelf.p4( "sync -k //...#none" )

# Change the clientspec
logging.info( 'Switching current clientspec to the fresh branch' )
clientspaceSwitch(clientname, newClientPath)

# Create new root
if not inplaceOperations:
os.mkdir( rootDirectory )

# Sync to the new data
if inplaceOperations:
p4shelf.p4( "sync -k -f //..." )
else:
logging.info( 'Syncing to the new branch...' )
p4shelf.p4( "sync //...#head" )

# Unshelf the work
logging.info( 'Restoring work from old clientspec...' )
p4shelf.main( ['-r', '-d', '-y', '-c', clientname, tempfilename] )

# Magic! We're back at the same state as before the switch!
return 0

def main( argv ):
"""
p4branch [options] <clientname> <new location in depot>

Options:

-v verbose
-s switch only, don't branch files first
-e live on the edge, don't backup and do operations
inplace (potentially dangerous, but faster on large repositories)

Example new locations must be written in perforce depot format, e.g.

p4branch myclient //depot/alpha1/...

2008 Jim Tilander (http://www.tilander.org/aurora)
"""
try:
opts, args = getopt.getopt( argv, 'vshe' )
except getopt.GetoptError:
print 'Error parsing arguments'
print main.__doc__
return 1

verbose = 0
doBranching = 1
skipBackups = 0
inplaceOperations =0
for o,a in opts:
if '-v' == o:
verbose = 1
if '-h' == o:
print HELP
return 1
if '-s' == o:
doBranching = 0
if '-e' == o:
#Living on the edge.
skipBackups = 1
inplaceOperations =1

if len(args) != 2:
print 'You must give both a clientspec and a target branch path'
print main.__doc__
return 1

clientname = args[0]
newClientPath = args[1]
if verbose:
logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(levelname)-7s: %(message)s' )
else:
logging.basicConfig( level=logging.INFO, format=os.path.basename(sys.argv[0]) + ': %(message)s' )

return doit(clientname, newClientPath, doBranching, skipBackups, inplaceOperations)

if __name__ == '__main__':
sys.exit( main(sys.argv[1:] ) )
Show details Hide details

Change log

r5 by jim.tilander on Sep 14, 2008   Diff
added support for doing inplace
operations, for really big depots this
might be a win
Go to: 
Project members, sign in to write a code review

Older revisions

r4 by jim.tilander on Sep 13, 2008   Diff
updated docs
r3 by jim.tilander on Sep 13, 2008   Diff
- Added the first revision of the
p4branch script and also modified the
shelf script to support it.

All revisions of this file

File info

Size: 8103 bytes, 216 lines
Hosted by Google Code