gitstats: Added a way to specify the path to use instead of the default
[git-stats.git] / src / scripts / setupRepo.py
blobaaa2e114a240900fdc1f33b7da33b11d97cd9323
1 #!/usr/bin/env python
3 import os
4 import tempfile
6 from git import Repo
8 testPath = os.path.join(tempfile.gettempdir(), "testrepo")
9 metricsPath = os.path.join(tempfile.gettempdir(), "metricsrepo")
11 class InitializationException(Exception):
12 """This exception is raised when something goes wrong during initialization.
13 """
15 def setupRepo(path):
16 """Creates a fresh repo under the specified path
18 If the specified path exists an exception is raised.
20 Args:
21 path: The path where the new repo should be created at.
22 """
24 print("Creating a new repository in " + path)
26 # Check if the path exists already, if so bail out
27 if os.path.exists(path):
28 raise InitializationException(
29 "The specified path, " + path + ", exists already, "
30 "please remove it or change the target path.")
32 # Create a new repo
33 repo = Repo.create(path)
34 os.chdir(path)
36 def setDefaultTime():
37 """Configures GIT_COMMITTER_DATE and GIT_AUTHOR_DATE to a default value
39 The actual date set is 2005-05-26 23:30, which was taken
40 from t/t1400-update-ref.sh and then converted to UNIX
41 time manually.
42 """
44 os.environ["GIT_COMMITTER_DATE"] = "1211844600"
45 os.environ["GIT_AUTHOR_DATE"] = "1211844600"
46 os.environ["TZ"] = "UTC"
48 def setDefaultInfo():
49 """Configures GIT_COMMITTER_NAME etc. to a default value.
51 These values are taken from t/test-lib.sh from git.git.
52 """
54 os.environ["GIT_COMMITTER_NAME"] = "C O Mitter"
55 os.environ["GIT_COMMITTER_EMAIL"] = "committer@example.com"
56 os.environ["GIT_AUTHOR_NAME"] = "A U Thor"
57 os.environ["GIT_AUTHOR_EMAIL"] = "author@example.com"
59 def setSecondaryInfo():
60 """Configures GIT_COMMITTER_NAME etc. to a secondary value.
62 The values are the default values but in reverse.
63 """
65 os.environ["GIT_COMMITTER_NAME"] = "Retti Mmoc"
66 os.environ["GIT_COMMITTER_EMAIL"] = "retti.mmoc@example.com"
67 os.environ["GIT_AUTHOR_NAME"] = "Roh Tua"
68 os.environ["GIT_AUTHOR_EMAIL"] = "roh.tua@example.com"
70 def bumpTime(amount = 60*60):
71 """Bumps the date in GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
73 Args:
74 amount: The amount by which they should be bumped.
75 """
77 time = os.environ["GIT_AUTHOR_DATE"]
78 time = int(time)
79 time += amount
80 time = str(time)
82 os.environ["GIT_AUTHOR_DATE"] = time
83 os.environ["GIT_COMMITTER_DATE"] = time
86 def addFile(filename, createFile=True, commit=True):
87 """Adds the specified file
89 Args:
90 filename: The name of the file to add.
91 createFile: Whether the file should be created.
92 """
94 if createFile:
95 file = open(filename, "w")
96 file.close()
98 git = Repo(".").git
100 git.add(filename)
102 if commit:
103 git.commit("-m", "Added file " + filename)
104 bumpTime()
106 def deleteFile(filename, leaveOnDisk=False):
107 """Deletes the specified file
109 Args:
110 filename: The name of the file to delete.
111 leaveOnDisk: Whether the file should not be physically deleted.
114 args = []
116 # If the file should not be deleted, tell 'git rm' so
117 if leaveOnDisk:
118 args.append("--cached")
120 args.append(filename)
122 git = Repo(".").git
124 git.rm(*args)
126 git.commit("-m", "Deleted file " + filename)
127 bumpTime()
129 def createLinearHistory(filename, createFile=False, initialCommit=False, start=1, count=10, finalCommit=False):
130 """Creates a linear history in the directory of the current repository.
132 The history is fairly simple and is all acted upon the specified file.
133 Any exceptions thrown during IO operations are _not_ cought.
135 Params:
136 filename: The name of the file in which to generate the history.
137 createFile: Whether the specified file has to be created first.
138 initialCommit: Whether to create an initial commit.
139 start: Which commit to start the history at.
140 count: The size of the history.
141 finalCommit: Whether to create a final commit.
144 git = Repo(".").git
146 stop = start + count
148 if createFile:
149 addFile(filename, commit=False)
151 # Create or open the content file
152 file = open(filename, "a")
154 if initialCommit:
155 # Start out with an initial change
156 file.write("Initial change\n")
157 file.flush()
159 # Add the file and create the initial commit
160 git.commit("-a", "-m", "Initial commit")
161 bumpTime()
163 # Create a linear history
164 for i in range(start, stop):
165 file.write("Change " + str(i) + "\n")
166 file.flush()
168 git.commit("-a", "-m", "Commit " + str(i))
169 bumpTime()
171 if finalCommit:
172 # Finish it up with a final change
173 file.write("Last change\n")
174 file.flush()
176 # And a final commit
177 git.commit("-a", "-m", "Last commit")
178 bumpTime()
180 def createBranch(name, checkout=True):
181 """Creates a branch with the specified name and optionally checks it out
183 Params:
184 name: The name of the new branch.
185 checkout: Whether to perform a checkout of the branch.
188 git = Repo(".").git
190 git.branch(name)
191 bumpTime()
193 if checkout:
194 checkoutBranch(name)
196 def deleteBranch(name):
197 """Deletes a branch
199 The branch that is to be deleted should be a subset of the current branch.
201 Params:
202 name: The name of the branch to delete.
205 git = Repo(".").git
207 git.branch("-d", name)
208 bumpTime()
210 def createDir(name, changeDir=False):
211 """Creates a directory with the specified name
213 Args:
214 name: The name of the directory
217 os.mkdir(name)
219 def checkoutBranch(name):
220 """Switches to the specified branch
222 Params:
223 name: The name of the branch to be switched to.
226 git = Repo(".").git
228 git.checkout(name)
230 def checkoutRelease(release):
231 """Switches to the specified release
233 Params:
234 release: The release to check out
237 checkoutBranch('v' + str(release))
239 def mergeBranch(name):
240 """Merges the current branch with the specified branch
242 Params:
243 name: The name of the branch to merge with.
246 git = Repo(".").git
248 git.merge(name)
249 bumpTime()
251 def revertLastCommit(commitChanges=True):
252 """Reverts the last commit made
254 Params:
255 commitChanges: Whether to commit the revert.
258 git = Repo(".").git
260 options = ["--no-edit", "HEAD"]
262 if not commitChanges:
263 options.append("-n")
265 git.revert(*options)
267 if commitChanges:
268 bumpTime()
270 def tagRelease(releaseNumber, head="HEAD"):
271 """Tags a release.
273 The created tag is 'v' + releaseNumber.
275 Params:
276 releaseNumber: The number of the release.
279 git = Repo(".").git
281 git.tag('v' + str(releaseNumber), head)
283 def createRemoteBranch(name, start="HEAD"):
284 """Creates a new remote branch with the specified name
286 Args:
287 name: The plain name of the remote (no '/refs/remotes/' prefix)
288 start: The revision to branch off from
291 git = Repo(".").git
293 git.update_ref('refs/remotes/' + name, start)
294 bumpTime()
296 def checkHead(HEAD):
297 """Verifies that the HEAD is as expected.
299 Params:
300 HEAD: The expected value of HEAD.
303 git = Repo(".").git
305 result = git.rev_parse("HEAD")
307 # Eat the trailing newline
308 currentHEAD = result[:-1]
310 scriptSuccessful = (HEAD == currentHEAD)
312 if scriptSuccessful:
313 print("Done, repo created successfully")
314 return True
315 else:
316 print("Something went wrong, current HEAD doesn't match.")
317 print("Expected: '" + HEAD + "'.")
318 print("Actual: '" + currentHEAD + "'.")
319 return False
321 def createTestRepository():
322 """Creates a test repository under setupRepo.testpath
325 # Directories
326 d = "docs"
328 # Files
329 c = "content.txt"
330 n = "notes.txt"
331 t = "test.c"
332 r = os.path.join(d, "README.txt")
334 # Branches
335 m = "master"
336 mt = "maint"
337 p = "pu"
338 e = "erase"
339 j = "junio"
341 # Start afresh
342 setupRepo(testPath)
344 # Set the default author/committer
345 setDefaultInfo()
346 setDefaultTime()
348 # Create some linear history
349 createLinearHistory(c, createFile=True, initialCommit=True, finalCommit=True)
351 # Create a maintenance branch
352 createBranch(mt)
354 # Create some history there too
355 createLinearHistory(n, createFile=True, count=3)
357 # Go back to master and merge with maint
358 checkoutBranch(m)
359 mergeBranch(mt)
361 # Create a playground branch and create some history
362 # This branch will be left 'dead', e.g., unused after this
363 createBranch(p)
364 createLinearHistory(t, createFile=True, count=7, finalCommit=True)
366 # Start out with release 0
367 tagRelease(0, head="HEAD~3")
369 # Revert that commit
370 revertLastCommit()
372 # Go back to master and continue some history
373 checkoutBranch(m)
374 createLinearHistory(c, start=10, count=5)
376 # Yay, our first release!
377 tagRelease(1)
379 # Merge current master into maint
380 checkoutBranch(mt)
381 mergeBranch(m)
383 # Ouch! Brown paper bag fix there, correct it and merge into master
384 revertLastCommit(commitChanges=False)
385 createLinearHistory(c, start=42, count=1)
386 checkoutBranch(m)
387 mergeBranch(mt)
389 # Continue some work on master
390 createLinearHistory(c, start=16, count=6)
392 # Have someone else do some work on master
393 setSecondaryInfo()
394 createLinearHistory(c, start=22, count=2)
396 # Go back to the initial data
397 setDefaultInfo()
399 # Create a directory for the documentation
400 createDir(d)
402 # Create a readme and add some content to it
403 createLinearHistory(r, createFile=True, count=5, finalCommit=True)
404 tagRelease(2)
406 # Ah, but that last one was bad, we don't want it
407 revertLastCommit()
409 # Instead, continue the linear history
410 createLinearHistory(r, start=6, count=1)
411 tagRelease(3)
413 # Come to think of it, that -was- a really good change after all
414 revertLastCommit()
416 # Reinstate that final commit
417 createLinearHistory(r, count=0, finalCommit=True)
419 tagRelease(4, head="HEAD~1")
421 # Create a branch to delete a file on
422 createBranch(e)
424 # Remove a file from git
425 deleteFile(r, leaveOnDisk=True)
427 # Only to add it back later so that we don't have any dangling files
428 addFile(r, createFile=False)
430 # Create a release at this point for easy switching later
431 tagRelease(5)
433 # Switch back to master for the test suite
434 checkoutBranch(m)
436 # Create a remote branch
437 createRemoteBranch(j, start="HEAD^")
439 return 0
441 def createMetricsRepository():
442 """Creates a metrics repository in setupRepo.metricspath
445 # files
446 c = "content.txt"
447 n = "notes.txt"
448 r = "README"
449 l = "limbo.dat"
451 # branches
452 m = "master"
453 mt = "maint"
454 h = "help"
455 s = "side"
457 # Create a repository to work in
458 setupRepo(metricsPath)
460 # Set the info
461 setDefaultInfo()
462 setDefaultTime()
464 # And create some history
465 createLinearHistory(c, createFile=True, count=2)
467 # Mark this commit so that we can jump back to it later
468 tagRelease(1)
470 # And create some more history
471 createLinearHistory(c, start=3, count=3)
473 # Go back to that marked commit
474 checkoutRelease(1)
476 # Now create a new branch to work on
477 createBranch(mt)
479 # Fix us up some more history
480 createLinearHistory(n, createFile=True, count=2)
482 # Mark this commit too so we can jump back
483 tagRelease(2)
485 # And make some more history
486 createLinearHistory(n, start=2, count=1)
488 # Now merge this back into master
489 checkoutBranch(m)
490 mergeBranch(mt)
492 # Go back to maint to create some more there
493 checkoutBranch(mt)
494 createLinearHistory(n, start=3, count=1)
496 # Now let's go back and fix that one commit
497 checkoutRelease(2)
498 createBranch(h)
499 createLinearHistory(r, createFile=True, count=2)
501 # Go back to maint, and merge it in
502 checkoutBranch(mt)
503 mergeBranch(h)
505 # Now that we merged that one in, kick it out
506 deleteBranch(h)
508 # We can create some more history here
509 createLinearHistory(n, start=4, count=3)
511 # Remember for branching off later
512 tagRelease(3)
514 # And a healthy bit of content on master too
515 checkoutBranch(m)
516 createLinearHistory(c, start=6, count=4)
518 # Create one more commit
519 checkoutBranch(mt)
520 createLinearHistory(n, start=7, count=1)
522 # Merge master in here
523 mergeBranch(m)
525 # And create another commit
526 createLinearHistory(n, start=8, count=1)
528 # Hacking on master some more
529 checkoutBranch(m)
530 createLinearHistory(c, start=10, count=4)
532 # Go back a bit and branch off
533 checkoutRelease(3)
534 createBranch(s)
535 createLinearHistory(l, createFile=True, count=3)
537 # Merge back into maint and work some more
538 checkoutBranch(mt)
539 mergeBranch(s)
540 createLinearHistory(n, start=9, count=1)
542 # Merge that into master and finish up
543 checkoutBranch(m)
544 mergeBranch(mt)
545 createLinearHistory(c, start=14, count=1)
547 # Finish side branch
548 checkoutBranch(s)
549 createLinearHistory(l, start=4, count=4)
551 # Lastly finish up maint
552 checkoutBranch(mt)
553 createLinearHistory(n, start=10, count=2)
555 return 0
557 def main(args):
558 """Creates a test and a metrics repository
560 Args:
561 args: An array of arguments, when 2 in size the second
562 element should either be 'test' to just create the test
563 repo, or 'metrics' to just create the metrics
564 repository. If both should be created then the size of
565 args should be 1. When the third argument is 'path',
566 instead of creating the repository the path where it
567 will be created is printed. If there is a fourth
568 argument it is interpreted as the path where the
569 repository should be created. The first element in the
570 array is always ignored.
572 Returns: 0 upon success, or nonzero upon failure.
575 global testPath
576 global metricsPath
578 size = len(args)
580 def printUsage():
581 str = "Please specify either 'test', 'metrics', or nothing to run both." \
582 "\nSuffix with 'path' to print the path where the repo will be created." \
583 "\nAs third argument you may provide the path to create the repo in."
585 print(str)
587 if size > 4:
588 printUsage()
589 return 129
591 if size == 3:
592 if args[2] != "path":
593 printUsage()
594 return 129
596 createTestRepo = False
597 createMetricsRepo = False
598 printTestRepoPath = False
599 printMetricsRepoPath = False
601 if size == 1:
602 createTestRepo = True
603 createMetricsRepo = True
605 if size == 2 or size == 4:
606 if args[1] == "test":
607 createTestRepo = True
609 if args[1] == "metrics":
610 createMetricsRepo = True
612 if size == 3:
613 if args[1] == "test":
614 printTestRepoPath = True
616 if args[1] == "metrics":
617 printMetricsRepoPath = True
619 if not createTestRepo and not createMetricsRepo \
620 and not printTestRepoPath and not printMetricsRepoPath:
621 printUsage()
622 return 129
624 if createTestRepo:
625 if size == 4:
626 testPath = args[3]
628 ret = createTestRepository()
629 if ret != 0:
630 return ret
632 if createMetricsRepo:
633 if size == 4:
634 metricsPath = args[3]
636 ret = createMetricsRepository()
637 if ret != 0:
638 return ret
640 if printTestRepoPath:
641 print(testPath)
643 if printMetricsRepoPath:
644 print(metricsPath)
646 return 0
648 if __name__ == '__main__':
649 import sys
650 ret = main(sys.argv)
651 sys.exit(ret)