public
Description: A distributed issue tracking system based on Git repositories, written in Python
Homepage: http://www.newartisans.com/software.html
Clone URL: git://github.com/jwiegley/git-issues.git
Click here to lend your support to: git-issues and make a donation at www.pledgie.com !
Created a gitshelve Python object, for easily writing scripts that store
arbitrary data inside a Git repository.
jwiegley (author)
Wed May 14 18:32:03 -0700 2008
commit  b6d2d87dc2011d19945e4484137ffbd1377f0aed
tree    38f51ce848206efb63dabd8d86b50705994f78b3
parent  cab44d22ec25ed063f4c772a8cf1903c7b283246
...
773
774
775
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
776
777
778
...
941
942
943
944
945
946
 
947
948
949
 
 
 
 
 
 
950
951
952
953
954
 
 
955
956
 
957
958
959
960
961
962
963
 
 
 
 
964
965
966
967
 
 
 
968
969
970
971
972
 
 
 
 
973
974
 
975
976
 
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1036
1037
1038
1039
1040
1041
 
 
 
1042
1043
 
1044
1045
1046
 
 
1047
1048
1049
1050
1051
1052
 
 
 
1053
1054
1055
1056
 
 
 
1057
1058
1059
1060
1061
 
 
1062
1063
 
1064
1065
1066
1067
 
1068
1069
...
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
...
1121
1122
1123
 
 
 
1124
1125
 
 
1126
1127
1128
1129
1130
1131
1132
1133
1134
 
 
1135
1136
1137
 
1138
1139
1140
1141
 
 
 
 
1142
1143
1144
1145
1146
 
 
 
1147
1148
1149
1150
 
 
 
 
1151
1152
1153
1154
1155
 
1156
1157
 
1158
1159
1160
1161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
 
 
 
1221
1222
1223
1224
 
1225
1226
 
 
1227
1228
1229
1230
1231
 
 
 
1232
1233
1234
1235
 
 
 
1236
1237
1238
1239
1240
1241
 
 
1242
1243
1244
 
1245
1246
1247
1248
 
1249
1250
1251
0
@@ -773,6 +773,186 @@ def git(cmd, *args, **kwargs):
0
     if not kwargs.has_key('ignore_output'):
0
         return proc.stdout.read()[:-1]
0
 
0
+class gitbook:
0
+ """Abstracts a reference to a data file within a Git repository. It also
0
+ maintains knowledge of whether the object has been modified or not."""
0
+ def __init__(self, shelf, path, hash = None):
0
+ self.shelf = shelf
0
+ self.path = path
0
+ self.hash = hash
0
+ self.data = None
0
+ self.dirty = False
0
+
0
+ def data(self):
0
+ if self.data is None:
0
+ assert self.hash is not None
0
+ self.data = git('cat-file', 'blob', self.hash)
0
+ return self.data
0
+
0
+ def set_data(self, data):
0
+ self.hash = None
0
+ self.data = data
0
+ self.mark_dirty()
0
+
0
+ def mark_dirty(self):
0
+ self.dirty = True
0
+
0
+ def __getstate__(self):
0
+ odict = self.__dict__.copy() # copy the dict since we change it
0
+ del odict['dirty'] # remove dirty flag
0
+ return odict
0
+
0
+ def __setstate__(self,dict):
0
+ self.__dict__.update(dict) # update attributes
0
+ self.dirty = False
0
+
0
+
0
+class gitshelve(dict):
0
+ """This class implements a Python "shelf" using a branch within a Git
0
+ repository. There is no "writeback" argument, meaning changes are only
0
+ written upon calling close or sync.
0
+
0
+ This implementation uses a dictionary of gitbook objects, since we don't
0
+ really want to use Pickling within a Git repository (it's not friendly to
0
+ other Git users, nor does it support merging)."""
0
+ ls_tree_pat = re.compile('(040000 tree|100644 blob) ([0-9a-f]{40})\t(start|(.+))$')
0
+
0
+ def __init__(self, branch):
0
+ self.dirty = False
0
+ self.branch = branch
0
+ self.trees = {}
0
+ self.paths = {}
0
+
0
+ def create_branch(self, cmd, args, kwargs):
0
+ """If an issues branch already exists at the remote, we simply refer
0
+ to it from now on. Otherwise, we create a dummy commit in order get
0
+ things started."""
0
+ try:
0
+ hash = git('rev-parse', 'origin/%s' % self.branch,
0
+ no_stderr = True)
0
+ except:
0
+ msg = "Created %s branch\n" % self.branch
0
+ hash = git('hash-object', '-w', '--stdin', input = msg)
0
+ hash = git('mktree', input = "100644 blob %s\tstart\n" % hash)
0
+ hash = git('commit-tree', hash, input = msg)
0
+
0
+ git('branch', self.branch, hash)
0
+ return True
0
+
0
+ def current_head(self):
0
+ return git('rev-parse', self.branch, restart = self.create_branch)
0
+
0
+ def open(cls, branch):
0
+ shelf = gitshelve(branch)
0
+
0
+ ls_tree = string.split(git('ls-tree', '-r', '-t', '-z',
0
+ self.current_head()), '\0')
0
+ for line in ls_tree:
0
+ match = cls.ls_tree_pat.match(line)
0
+ assert match
0
+
0
+ hash = match.group(2)
0
+ path = match.group(3)
0
+
0
+ if match.group(1) == "040000 tree":
0
+ parts = os.path.split(path)
0
+
0
+ dict = shelf.trees
0
+
0
+ for part in parts:
0
+ if not dict.has_key(part):
0
+ dict[part] = {}
0
+ dict = dict[part]
0
+
0
+ dict['__root__'] = hash
0
+
0
+ elif path == 'start':
0
+ break
0
+
0
+ else:
0
+ assert not shelf.paths.has_key(path)
0
+ shelf.paths[path] = gitbook(shelf, path, hash)
0
+
0
+ return shelf
0
+
0
+ open = classmethod(open)
0
+
0
+ def sync(self):
0
+ if self.dirty:
0
+ for book in self.paths:
0
+ if book.dirty:
0
+ self.sync_book(book)
0
+ self.dirty = False
0
+
0
+ def sync_book(self, book):
0
+ # Create a unique blob to represent the new issue. This is the
0
+ # issue's official object name from now on, and will never change.
0
+ blob = git('hash-object', '-w', '--stdin', input = repr(book))
0
+ book.hash = blob
0
+ book.dirty = False
0
+
0
+ tree = git('mktree', input = "100644 blob %s\tinfo\n" % blob)
0
+
0
+ # Merge this blob with existing issue blobs that share the same
0
+ # first two hash digits
0
+ if self.trees.has_key(blob[:2]):
0
+ parent = self.trees[blob[:2]]
0
+ else:
0
+ parent = [None]
0
+ self.trees[blob[:2]] = parent
0
+ parent.append((tree, blob[2:]))
0
+
0
+ buffer = StringIO()
0
+ for child in parent[1:]:
0
+ buffer.write("040000 tree %s\t%s\n" % (child[0], child[1]))
0
+
0
+ tree = git('mktree', input = buffer.getvalue())
0
+
0
+ self.trees[blob[:2]][0] = tree
0
+
0
+ # Merge it into the tree of issues overall
0
+ keys = self.trees.keys()
0
+ keys.sort()
0
+
0
+ buffer = StringIO()
0
+ for key in keys:
0
+ buffer.write("040000 tree %s\t%s\n" % (self.trees[key][0], key))
0
+
0
+ tree = git('mktree', input = buffer.getvalue())
0
+
0
+ # Commit the merged tree (though at this moment it's a dangling commit)
0
+ commit = git('commit-tree', tree, '-p', self.current_head(),
0
+ input = book.title)
0
+
0
+ # Update the head of the issues branch to point to the new commit
0
+ self.update_head(commit)
0
+
0
+ def close(self):
0
+ if self.dirty:
0
+ self.sync()
0
+ del self.objects # free it up right away
0
+
0
+ def __getitem__(self, path):
0
+ return self.paths[path].data()
0
+
0
+ def __setitem__(self, path, data):
0
+ if not self.paths.has_key(path):
0
+ self.paths[path] = gitbook(self, path)
0
+ self.paths[path].set_data(data)
0
+ self.dirty = True
0
+
0
+ def __detitem__(self, path):
0
+ del self.paths[path]
0
+
0
+ def __contains__(self, path):
0
+ return self.paths.has_key()
0
+
0
+ def __iter__(self):
0
+ pass
0
+
0
+ def iteritems(self):
0
+ pass
0
+
0
 class GitIssueSet(IssueSet):
0
     """This object implements all the command necessary to interact with Git
0
     for the purpose of storing and distributing issues."""
0
@@ -941,129 +1121,131 @@ def get_issue(issueSet, id):
0
 
0
 ######################################################################
0
 
0
-if len(args) == 0:
0
- print "Show help here."
0
- sys.exit(1)
0
+if __name__ == '__main__':
0
 
0
-command = args[0]
0
-args = args[1:]
0
+ if len(args) == 0:
0
+ print "Show help here."
0
+ sys.exit(1)
0
+
0
+ command = args[0]
0
+ args = args[1:]
0
 
0
 ######################################################################
0
 
0
-# jww (2008-05-12): Pick the appropriate IssueSet to used based on what we
0
-# find in our environment.
0
+ # jww (2008-05-12): Pick the appropriate IssueSet to used based on what we
0
+ # find in our environment.
0
 
0
-issueSet = IssueSet.load_state(GitIssueSet())
0
+ issueSet = IssueSet.load_state(GitIssueSet())
0
 
0
 ######################################################################
0
 
0
-if command == "list":
0
- print
0
- print " # Id Title State Date Assign Tags"
0
- print "-------------------------------------------------------------------------------"
0
+ if command == "list":
0
+ print
0
+ print " # Id Title State Date Assign Tags"
0
+ print "-------------------------------------------------------------------------------"
0
 
0
- index = 1
0
- for name in issueSet.issues.keys():
0
- issue = issueSet.issues[name]
0
+ index = 1
0
+ for name in issueSet.issues.keys():
0
+ issue = issueSet.issues[name]
0
 
0
- print "%4d %s %-23s %-6s %5s %6s %s" % \
0
- (index, issue.name[:7], issue.title, issue.status,
0
- issue.created and issue.created.strftime('%m/%d'),
0
- str(issue.author)[:6], '')
0
+ print "%4d %s %-23s %-6s %5s %6s %s" % \
0
+ (index, issue.name[:7], issue.title, issue.status,
0
+ issue.created and issue.created.strftime('%m/%d'),
0
+ str(issue.author)[:6], '')
0
 
0
- index += 1
0
+ index += 1
0
 
0
- print
0
+ print
0
 
0
 ######################################################################
0
 
0
-elif command == "show" or command == "dump":
0
- if len(args) == 0:
0
- print "Shows needs an index or Id."
0
-
0
- issue = get_issue(issueSet, args[0])
0
- if command == "show":
0
- sys.stdout.write("""
0
- Title: %s
0
- Summary: %s
0
-
0
-Description: %s
0
-
0
- Author: %s
0
- Reporter: %s
0
- Owner: %s
0
- Assigned: %s
0
- Cc: %s
0
-
0
- Type: %s
0
- Status: %s
0
- Resolution: %s
0
- Components: %s
0
- Version: %s
0
- Milestone: %s
0
- Severity: %s
0
- Priority: %s
0
- Tags: %s
0
-
0
- Created: %s
0
- Modified: %s
0
-
0
-""" % (issue.title,
0
- issue.format_long_text(issue.summary),
0
-
0
- issue.format_long_text(issue.description),
0
-
0
- issue.author,
0
- issue.format_people_list(issue.reporters),
0
- issue.format_people_list(issue.owners),
0
- issue.format_people_list(issue.assigned),
0
- issue.format_people_list(issue.carbons),
0
-
0
- issue.issue_type,
0
- issue.status,
0
- issue.resolution or "",
0
- issue.components or "",
0
- issue.version or "",
0
- issue.milestone or "",
0
- issue.severity,
0
- issue.priority,
0
- issue.tags or "",
0
-
0
- issue.created,
0
- issue.modified))
0
- else:
0
- write_object(issue)
0
+ elif command == "show" or command == "dump":
0
+ if len(args) == 0:
0
+ print "Shows needs an index or Id."
0
+
0
+ issue = get_issue(issueSet, args[0])
0
+ if command == "show":
0
+ sys.stdout.write("""
0
+ Title: %s
0
+ Summary: %s
0
+
0
+ Description: %s
0
+
0
+ Author: %s
0
+ Reporter: %s
0
+ Owner: %s
0
+ Assigned: %s
0
+ Cc: %s
0
+
0
+ Type: %s
0
+ Status: %s
0
+ Resolution: %s
0
+ Components: %s
0
+ Version: %s
0
+ Milestone: %s
0
+ Severity: %s
0
+ Priority: %s
0
+ Tags: %s
0
+
0
+ Created: %s
0
+ Modified: %s
0
+
0
+ """ % (issue.title,
0
+ issue.format_long_text(issue.summary),
0
+
0
+ issue.format_long_text(issue.description),
0
+
0
+ issue.author,
0
+ issue.format_people_list(issue.reporters),
0
+ issue.format_people_list(issue.owners),
0
+ issue.format_people_list(issue.assigned),
0
+ issue.format_people_list(issue.carbons),
0
+
0
+ issue.issue_type,
0
+ issue.status,
0
+ issue.resolution or "",
0
+ issue.components or "",
0
+ issue.version or "",
0
+ issue.milestone or "",
0
+ issue.severity,
0
+ issue.priority,
0
+ issue.tags or "",
0
+
0
+ issue.created,
0
+ issue.modified))
0
+ else:
0
+ write_object(issue)
0
 
0
 ######################################################################
0
 
0
-elif command == "change":
0
- if len(args) == 0:
0
- print "Change needs an issue."
0
+ elif command == "change":
0
+ if len(args) == 0:
0
+ print "Change needs an issue."
0
 
0
- issue = get_issue(issueSet, args[0])
0
+ issue = get_issue(issueSet, args[0])
0
 
0
- # jww (2008-05-13): Need to parse datetime, lists, and people
0
- eval ("issue.set_%s(\"%s\")" % (args[1], args[2]))
0
+ # jww (2008-05-13): Need to parse datetime, lists, and people
0
+ eval ("issue.set_%s(\"%s\")" % (args[1], args[2]))
0
 
0
 ######################################################################
0
 
0
-elif command == "new":
0
- if len(args) == 0:
0
- print "New needs a title."
0
+ elif command == "new":
0
+ if len(args) == 0:
0
+ print "New needs a title."
0
 
0
- issue = Issue(issueSet, issueSet.current_author(), args[0])
0
- issueSet.add_issue(issue)
0
- issue.mark_dirty(True)
0
+ issue = Issue(issueSet, issueSet.current_author(), args[0])
0
+ issueSet.add_issue(issue)
0
+ issue.mark_dirty(True)
0
 
0
 ######################################################################
0
 
0
-# If any of the commands made the issueSet dirty, (possibly) update the
0
-# repository and write out a new cache
0
+ # If any of the commands made the issueSet dirty, (possibly) update the
0
+ # repository and write out a new cache
0
 
0
-issueSet.save_state()
0
+ issueSet.save_state()
0
 
0
 ######################################################################
0
 
0
-sys.exit(0)
0
+ sys.exit(0)
0
 
0
 # git-issue ends here

Comments

    No one has commented yet.