From f51e11fc02789e55895fbae970e3b2b471ca1ceb Mon Sep 17 00:00:00 2001 From: lizherui Date: Mon, 16 Jun 2014 17:32:22 +0800 Subject: [PATCH 01/29] add the rm command --- Schedule.md | 2 ++ src/command.py | 6 ++++++ src/config.py | 2 +- src/git.py | 27 ++++++++++++++++++++++++++- src/index.py | 16 +--------------- src/repository.py | 7 ++++++- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/Schedule.md b/Schedule.md index d77ae9e..1772022 100644 --- a/Schedule.md +++ b/Schedule.md @@ -2,6 +2,7 @@ * init * add * commit +* rm ###Doing * push @@ -10,3 +11,4 @@ * clone * log * diff +...... diff --git a/src/command.py b/src/command.py index 008bd35..cc79740 100644 --- a/src/command.py +++ b/src/command.py @@ -31,6 +31,12 @@ def cmd_add(workspace, file): else: Repository(workspace).stage([file]) + @staticmethod + def cmd_rm(workspace, file, cached): + Repository(workspace).delete(file) + if not cached: + os.remove(file) + @staticmethod def cmd_commit(workspace, msg): Repository(workspace).commit(msg) diff --git a/src/config.py b/src/config.py index cb3e9c1..505e107 100644 --- a/src/config.py +++ b/src/config.py @@ -19,7 +19,7 @@ def __init__(self, workspace): ''' self.workspace = workspace self.config_dict = {} - paths = ['/etc/config', os.path.expanduser('~') + '/.gitconfig', os.path.join(workspace, '.git', 'config')] + paths = ['/etc/gitconfig', os.path.expanduser('~') + '/.gitconfig', os.path.join(workspace, '.git', 'config')] for path in paths: if os.path.exists(path): self._parse_config_to_dict(path) diff --git a/src/git.py b/src/git.py index ea1a9b4..2963d6a 100755 --- a/src/git.py +++ b/src/git.py @@ -59,10 +59,32 @@ def __init__(self, argv): }, ] }, + + 'rm' : { + 'func' : self._rm, + 'help' : 'Remove files from the working tree and from the index', + 'args' : [ + { + 'name' : ['file'], + 'properties' : + { + 'help' : 'Files to remove', + }, + }, + { + 'name' : ['--cached'], + 'properties' : + { + 'help' : 'Remove files only from the index', + 'action' : 'store_true', + }, + }, + ] + }, 'commit' : { 'func' : self._commit, - 'help' : 'Record changes to the repositoryx', + 'help' : 'Record changes to the repository', 'args' : [ { 'name' : ['-m', '--message'], @@ -94,6 +116,9 @@ def _init(self, args): def _add(self, args): Command.cmd_add(os.getcwd(), args.file) + + def _rm(self, args): + Command.cmd_rm(os.getcwd(), args.file, args.cached) def _commit(self, args): Command.cmd_commit(os.getcwd(), args.msg) diff --git a/src/index.py b/src/index.py index 223f3d2..11f22fb 100644 --- a/src/index.py +++ b/src/index.py @@ -120,18 +120,4 @@ def _build_tree(path): return newtree return _build_tree(tree) - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/repository.py b/src/repository.py index fc2fe12..c255a9b 100644 --- a/src/repository.py +++ b/src/repository.py @@ -106,4 +106,9 @@ def commit(self, msg, ref='HEAD'): commit = Commit(self.workspace, tree_sha1=cur_tree.sha1, parent_sha1=parent_sha1, name=committer_name, email=committer_email, \ timestamp=commit_time, timezone=commit_timezone, msg=msg) write_object_to_file(commit.path, commit.content) - write_to_file(ref_path, commit.sha1) \ No newline at end of file + write_to_file(ref_path, commit.sha1) + + def delete(self, file): + del self.index.entries[file] + self.index.write_to_file() + \ No newline at end of file From a7b8e6629c3d696934b6e27cf2e91747729ba02c Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 17 Jun 2014 20:06:08 +0800 Subject: [PATCH 02/29] add log command --- Schedule.md | 1 + src/command.py | 4 ++++ src/git.py | 23 ++++++++++++++++++- src/objects.py | 58 +++++++++++++++++++++++++++++------------------ src/repository.py | 47 ++++++++++++++++++++++++++++---------- src/utils.py | 8 +++++++ 6 files changed, 106 insertions(+), 35 deletions(-) diff --git a/Schedule.md b/Schedule.md index 1772022..48a8d84 100644 --- a/Schedule.md +++ b/Schedule.md @@ -3,6 +3,7 @@ * add * commit * rm +* log ###Doing * push diff --git a/src/command.py b/src/command.py index cc79740..92b1286 100644 --- a/src/command.py +++ b/src/command.py @@ -41,6 +41,10 @@ def cmd_rm(workspace, file, cached): def cmd_commit(workspace, msg): Repository(workspace).commit(msg) + @staticmethod + def cmd_log(workspace, num): + Repository(workspace).show_log(num) + @staticmethod def cmd_push(): pass diff --git a/src/git.py b/src/git.py index 2963d6a..5b8949a 100755 --- a/src/git.py +++ b/src/git.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- ''' Created on Jun 8, 2014 @@ -97,6 +97,24 @@ def __init__(self, argv): ], }, + 'log' : { + 'func' : self._log, + 'help' : 'Show commit logs', + 'args' : [ + { + 'name' : ['-n'], + 'properties' : + { + 'help' : 'Limit the number of commits to output', + 'nargs' : '?', + 'type' : int, + 'dest' : 'num', + 'default' : float('infinity'), + } + }, + ], + }, + 'push' : { 'func' : self._push, 'help' : 'Update remote refs along with associated objects', @@ -123,6 +141,9 @@ def _rm(self, args): def _commit(self, args): Command.cmd_commit(os.getcwd(), args.msg) + def _log(self, args): + Command.cmd_log(os.getcwd(), args.num) + def _push(self, args): pass diff --git a/src/objects.py b/src/objects.py index bb4c25c..596449c 100644 --- a/src/objects.py +++ b/src/objects.py @@ -6,50 +6,64 @@ import binascii import os +import re import zlib -from utils import cal_sha1 +from utils import cal_sha1, read_file class BaseObject(object): ''' git base object ''' - def __init__(self, workspace, content): + def __init__(self, workspace, final_content=None, sha1=None): ''' Constructor ''' - self.content = zlib.compress(content) - self.sha1 = cal_sha1(content) - self.path = os.path.join(workspace, '.git', 'objects', self.sha1[:2], self.sha1[2:]) + if sha1: + self.sha1 = sha1 + self.path = os.path.join(workspace, '.git', 'objects', self.sha1[:2], self.sha1[2:]) + self.content = read_file(self.path) + else: + self.content = zlib.compress(final_content) + self.sha1 = cal_sha1(final_content) + self.path = os.path.join(workspace, '.git', 'objects', self.sha1[:2], self.sha1[2:]) class Blob(BaseObject): - def __init__(self, workspace, content): - real_content = 'blob %d\0%s' % (len(content), content) - super(Blob, self).__init__(workspace, real_content) + def __init__(self, workspace, raw_content=None, sha1=None): + final_content = 'blob %d\0%s' % (len(raw_content), raw_content) + super(Blob, self).__init__(workspace, final_content) class Tree(BaseObject): - def __init__(self, workspace, args): - content = '' + def __init__(self, workspace, args=None, sha1=None): + raw_content = '' for arg in args: - content += '%04o %s\0%s' % (arg['mode'], arg['name'], binascii.unhexlify(arg['sha1'])) - real_content = 'tree %d\0%s' % (len(content), content) - super(Tree, self).__init__(workspace, real_content) + raw_content += '%04o %s\0%s' % (arg['mode'], arg['name'], binascii.unhexlify(arg['sha1'])) + final_content = 'tree %d\0%s' % (len(raw_content), raw_content) + super(Tree, self).__init__(workspace, final_content) class Commit(BaseObject): def __init__(self, workspace, **kwargs): - content = 'tree %s\n' % (kwargs['tree_sha1']) - if kwargs['parent_sha1']: - content += 'parent %s\n' % (kwargs['parent_sha1']) + if kwargs['sha1']: + super(Commit, self).__init__(workspace, sha1=kwargs['sha1']) + final_content = zlib.decompress(self.content) + res = re.findall('parent (\w+)\n', final_content) + self.parent_sha1 = res[0] if res else None + self.raw_content = final_content[final_content.find('tree'):] - content += 'author %s %s %s %s\ncommitter %s %s %s %s\n\n%s\n' \ - % (kwargs['name'], kwargs['email'], kwargs['timestamp'], kwargs['timezone'] , \ - kwargs['name'], kwargs['email'], kwargs['timestamp'], kwargs['timezone'] , kwargs['msg']) - - real_content = 'commit %d\0%s' % (len(content), content) - super(Commit, self).__init__(workspace, real_content) + else: + raw_content = 'tree %s\n' % (kwargs['tree_sha1']) + if kwargs['parent_sha1']: + raw_content += 'parent %s\n' % (kwargs['parent_sha1']) + + raw_content += 'author %s %s %s %s\ncommitter %s %s %s %s\n\n%s\n' \ + % (kwargs['name'], kwargs['email'], kwargs['timestamp'], kwargs['timezone'] , \ + kwargs['name'], kwargs['email'], kwargs['timestamp'], kwargs['timezone'] , kwargs['msg']) + + final_content = 'commit %d\0%s' % (len(raw_content), raw_content) + super(Commit, self).__init__(workspace, final_content) diff --git a/src/repository.py b/src/repository.py index c255a9b..2dcdff5 100644 --- a/src/repository.py +++ b/src/repository.py @@ -9,7 +9,8 @@ from config import Config from index import Index from objects import Blob, Commit -from utils import read_file, write_to_file, cal_mode, write_object_to_file +from utils import read_file, write_to_file, cal_mode, write_object_to_file, \ + less_str class Repository(object): @@ -42,6 +43,14 @@ def __init__(self, workspace): self.workspace = workspace self.index = Index(os.path.join(workspace, '.git', 'index')) self.config = Config(workspace) + self.head_path = self._get_head_path() + self.head_tree = None + if os.path.exists(self.head_path): + self.head_tree = read_file(self.head_path).strip() + + def _get_head_path(self): + branch_name = read_file(os.path.join(self.workspace, '.git', 'HEAD')).strip('\n').rsplit('/', 1)[-1] + return os.path.join(self.workspace, '.git', 'refs', 'heads', branch_name) def stage(self, files): try: @@ -89,26 +98,40 @@ def create_repository(workspace, bare=False): content = Config.create_config(init_config_dict) write_to_file('config', content) - def commit(self, msg, ref='HEAD'): - cur_tree = self.index.do_commit(self.workspace) - branch_name = read_file(os.path.join(self.workspace, '.git', 'HEAD')).strip('\n').rsplit('/', 1)[-1] - ref_path = os.path.join(self.workspace, '.git', 'refs', 'heads', branch_name) - parent_sha1 = None - if os.path.exists(ref_path): - parent_sha1 = read_file(ref_path) + def commit(self, msg): + new_tree = self.index.do_commit(self.workspace) + committer_name = self.config.config_dict['user']['name'] committer_email = '<%s>' % (self.config.config_dict['user']['email']) commit_time = int(time.time()) - - #TO FIX commit_timezone = time.strftime("%z", time.gmtime()) - commit = Commit(self.workspace, tree_sha1=cur_tree.sha1, parent_sha1=parent_sha1, name=committer_name, email=committer_email, \ + commit = Commit(self.workspace, sha1=None, tree_sha1=new_tree.sha1, parent_sha1=self.head_tree, name=committer_name, email=committer_email, \ timestamp=commit_time, timezone=commit_timezone, msg=msg) write_object_to_file(commit.path, commit.content) - write_to_file(ref_path, commit.sha1) + write_to_file(self.head_path, commit.sha1) def delete(self, file): del self.index.entries[file] self.index.write_to_file() + + def show_log(self, num): + cur_commit = Commit(self.workspace, sha1=self.head_tree) + print_str = cur_commit.raw_content + while num > 1 and cur_commit.parent_sha1: + num -= 1 + parent_commit = Commit(self.workspace, sha1=cur_commit.parent_sha1) + print_str += '\n%s' % (parent_commit.raw_content) + cur_commit = parent_commit + less_str(print_str) + + + + + + + + + + \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index 9f49609..6c67378 100644 --- a/src/utils.py +++ b/src/utils.py @@ -8,6 +8,7 @@ import os import stat import sys +import tempfile S_IFGITLINK = 0o160000 @@ -49,6 +50,13 @@ def cal_mode(mode): ret |= (mode & 0o111) return ret +def less_str(str): + f = tempfile.NamedTemporaryFile() + f.write(str) + f.seek(0) + os.system("cat %s | less" % f.name) + f.close() + class Sha1Reader(object): From c3c1ed5ca6f4ec3dc57ba05140155774de85ba1e Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 17 Jun 2014 20:18:20 +0800 Subject: [PATCH 03/29] update Schedule --- Schedule.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Schedule.md b/Schedule.md index 48a8d84..642b6b8 100644 --- a/Schedule.md +++ b/Schedule.md @@ -10,6 +10,7 @@ ###TODO * clone -* log * diff +* merge + ...... From eda2d4b7638bcad7e4fe0052de9d71b5ca90b94e Mon Sep 17 00:00:00 2001 From: lizherui Date: Thu, 19 Jun 2014 11:19:53 +0800 Subject: [PATCH 04/29] use 'with as' to read and write file instead --- src/utils.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/utils.py b/src/utils.py index 6c67378..f046327 100644 --- a/src/utils.py +++ b/src/utils.py @@ -7,16 +7,14 @@ import hashlib import os import stat -import sys import tempfile S_IFGITLINK = 0o160000 def write_to_file(path, content): - f = open(path, 'w') - f.write(content) - f.close() + with open(path, 'w') as f: + f.write(content) def write_object_to_file(path, content): dir = os.path.dirname(path) @@ -25,14 +23,8 @@ def write_object_to_file(path, content): write_to_file(path, content) def read_file(file_name): - try: - f = open(file_name, 'r') - content = f.read() - f.close() - return content - except Exception, e: - print "open file %s error: %s" % (file_name, e) - sys.exit(1) + with open(file_name, 'r') as f: + return f.read() def cal_sha1(content): sha1 = hashlib.sha1() @@ -51,12 +43,10 @@ def cal_mode(mode): return ret def less_str(str): - f = tempfile.NamedTemporaryFile() - f.write(str) - f.seek(0) - os.system("cat %s | less" % f.name) - f.close() - + with tempfile.NamedTemporaryFile() as f: + f.write(str) + f.seek(0) + os.system("cat %s | less" % f.name) class Sha1Reader(object): From 1719655ad8cc6b20323b42730945f68546f05998 Mon Sep 17 00:00:00 2001 From: lizherui Date: Sun, 22 Jun 2014 02:07:09 +0800 Subject: [PATCH 05/29] add status command --- src/constants.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/constants.py diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..c085991 --- /dev/null +++ b/src/constants.py @@ -0,0 +1,50 @@ +''' +Created on Jun 19, 2014 + +@author: lzrak47 +''' + +import os + +GIT_DIR = '.git' + +INDEX_PATH = os.path.join(GIT_DIR, 'index') + +HEAD_PATH = os.path.join(GIT_DIR, 'HEAD') + +CONFIG_PATH = os.path.join(GIT_DIR, 'config') + +DESCRIPTION_PATH = os.path.join(GIT_DIR, 'description') + +BRANCHES_DIR = os.path.join(GIT_DIR, 'branches') + +HOOK_DIR = os.path.join(GIT_DIR, 'hook') + +OBJECT_DIR = os.path.join(GIT_DIR, 'objects') +OBJECT_INFO_DIR = os.path.join(OBJECT_DIR, 'info') +OBJECT_PACK_DIR = os.path.join(OBJECT_DIR, 'pack') + +INFO_DIR = os.path.join(GIT_DIR, 'info') +INFO_EXCLUDE_PATH = os.path.join(INFO_DIR, 'exclude') + +REF_DIR = os.path.join(GIT_DIR, 'refs') +REF_HEADS_DIR = os.path.join(REF_DIR, 'heads') +REF_TAG_DIR = os.path.join(REF_DIR, 'tag') + +INIT_DIR = [ + BRANCHES_DIR, + HOOK_DIR, + INFO_DIR, + OBJECT_DIR, + OBJECT_PACK_DIR, + OBJECT_INFO_DIR, + REF_DIR, + REF_HEADS_DIR, + REF_TAG_DIR, +] + +INIT_FILE = [ + [HEAD_PATH, 'ref: refs/heads/master'], + [DESCRIPTION_PATH, 'Unnamed repository'], + [INFO_EXCLUDE_PATH, ''], +] From 1f2b53f2369719f20554a21c89479e380852354b Mon Sep 17 00:00:00 2001 From: lizherui Date: Sun, 22 Jun 2014 02:07:13 +0800 Subject: [PATCH 06/29] add status command --- src/command.py | 30 +++++------ src/config.py | 30 +++++------ src/git.py | 17 ++++-- src/index.py | 4 +- src/objects.py | 70 +++++++++++++----------- src/repository.py | 132 +++++++++++++++++++++++++++------------------- src/utils.py | 13 +++++ 7 files changed, 172 insertions(+), 124 deletions(-) diff --git a/src/command.py b/src/command.py index 92b1286..fd19569 100644 --- a/src/command.py +++ b/src/command.py @@ -5,7 +5,9 @@ ''' import os +from constants import GIT_DIR from repository import Repository +from utils import get_all_files_in_dir class Command(object): @@ -19,31 +21,29 @@ def cmd_init(workspace, bare): Repository.create_repository(workspace, bare) @staticmethod - def cmd_add(workspace, file): + def cmd_add(file): if file == '.': - file_list = [] - for root, dirs, files in os.walk('.'): - if ".git" in dirs: - dirs.remove('.git') - for file in files: - file_list.append(os.path.join(root[2:], file)) - Repository(workspace).stage(file_list) + Repository().stage(get_all_files_in_dir('.', GIT_DIR)) else: - Repository(workspace).stage([file]) + Repository().stage([file]) @staticmethod - def cmd_rm(workspace, file, cached): - Repository(workspace).delete(file) + def cmd_rm(file, cached): + Repository().delete(file) if not cached: os.remove(file) @staticmethod - def cmd_commit(workspace, msg): - Repository(workspace).commit(msg) + def cmd_commit(msg): + Repository().commit(msg) @staticmethod - def cmd_log(workspace, num): - Repository(workspace).show_log(num) + def cmd_log(num): + Repository().show_log(num) + + @staticmethod + def cmd_status(): + Repository().show_status() @staticmethod def cmd_push(): diff --git a/src/config.py b/src/config.py index 505e107..170fe39 100644 --- a/src/config.py +++ b/src/config.py @@ -5,26 +5,27 @@ ''' import os + +from constants import CONFIG_PATH from utils import read_file + class Config(object): ''' config file ''' - - def __init__(self, workspace): + def __init__(self): ''' Constructor ''' - self.workspace = workspace self.config_dict = {} - paths = ['/etc/gitconfig', os.path.expanduser('~') + '/.gitconfig', os.path.join(workspace, '.git', 'config')] + paths = ['/etc/gitconfig', os.path.expanduser('~') + '/.gitconfig', CONFIG_PATH] for path in paths: if os.path.exists(path): self._parse_config_to_dict(path) - - + + def _parse_config_to_dict(self, path): content = read_file(path) for entry in content.split('[')[1:]: @@ -36,22 +37,15 @@ def _parse_config_to_dict(self, path): key = key_val.split(' = ')[0].strip() val = key_val.split(' = ')[1].strip() self.config_dict[index][key] = val - - + + @staticmethod def create_config(config_dict): str = '' for index, key_value in config_dict.iteritems(): str += '[%s]\n' % (index) for key, value in key_value.iteritems(): - str +='\t%s = %s\n' % (key, value) + str += '\t%s = %s\n' % (key, value) return str - - - - - - - - - \ No newline at end of file + + diff --git a/src/git.py b/src/git.py index 5b8949a..8a94156 100755 --- a/src/git.py +++ b/src/git.py @@ -115,6 +115,12 @@ def __init__(self, argv): ], }, + 'status' : { + 'func' : self._status, + 'help' : 'Show the working tree status', + 'args' : [], + }, + 'push' : { 'func' : self._push, 'help' : 'Update remote refs along with associated objects', @@ -133,16 +139,19 @@ def _init(self, args): Command.cmd_init(workspace=workspace, bare=args.bare) def _add(self, args): - Command.cmd_add(os.getcwd(), args.file) + Command.cmd_add(args.file) def _rm(self, args): - Command.cmd_rm(os.getcwd(), args.file, args.cached) + Command.cmd_rm(args.file, args.cached) def _commit(self, args): - Command.cmd_commit(os.getcwd(), args.msg) + Command.cmd_commit(args.msg) def _log(self, args): - Command.cmd_log(os.getcwd(), args.num) + Command.cmd_log(args.num) + + def _status(self, args): + Command.cmd_status() def _push(self, args): pass diff --git a/src/index.py b/src/index.py index 11f22fb..acd61e2 100644 --- a/src/index.py +++ b/src/index.py @@ -96,7 +96,7 @@ def write_to_file(self): os.rename(lock_file, self.path) - def do_commit(self, workspace): + def do_commit(self): tree = {} for path, property in self.entries.iteritems(): t = tree @@ -115,7 +115,7 @@ def _build_tree(path): else: (mode, sha1) = entry file_arr.append({'name':name, 'mode':mode, 'sha1':sha1}) - newtree = Tree(workspace, sorted(dir_arr,key = lambda x:x['name']) + sorted(file_arr,key = lambda x:x['name'])) + newtree = Tree(sorted(dir_arr,key = lambda x:x['name']) + sorted(file_arr,key = lambda x:x['name'])) write_object_to_file(newtree.path, newtree.content) return newtree diff --git a/src/objects.py b/src/objects.py index 596449c..21b171d 100644 --- a/src/objects.py +++ b/src/objects.py @@ -7,8 +7,10 @@ import binascii import os import re +import stat import zlib +from constants import OBJECT_DIR from utils import cal_sha1, read_file @@ -16,42 +18,64 @@ class BaseObject(object): ''' git base object ''' - def __init__(self, workspace, final_content=None, sha1=None): + def __init__(self, final_content=None, sha1=None): ''' Constructor ''' if sha1: self.sha1 = sha1 - self.path = os.path.join(workspace, '.git', 'objects', self.sha1[:2], self.sha1[2:]) + self.path = os.path.join(OBJECT_DIR, self.sha1[:2], self.sha1[2:]) self.content = read_file(self.path) else: self.content = zlib.compress(final_content) self.sha1 = cal_sha1(final_content) - self.path = os.path.join(workspace, '.git', 'objects', self.sha1[:2], self.sha1[2:]) + self.path = os.path.join(OBJECT_DIR, self.sha1[:2], self.sha1[2:]) class Blob(BaseObject): - def __init__(self, workspace, raw_content=None, sha1=None): + def __init__(self, raw_content=None, sha1=None): final_content = 'blob %d\0%s' % (len(raw_content), raw_content) - super(Blob, self).__init__(workspace, final_content) + super(Blob, self).__init__(final_content) class Tree(BaseObject): - def __init__(self, workspace, args=None, sha1=None): - raw_content = '' - for arg in args: - raw_content += '%04o %s\0%s' % (arg['mode'], arg['name'], binascii.unhexlify(arg['sha1'])) - final_content = 'tree %d\0%s' % (len(raw_content), raw_content) - super(Tree, self).__init__(workspace, final_content) + def __init__(self, args=None, sha1=None): + if sha1: + super(Tree, self).__init__(sha1=sha1) + final_content = zlib.decompress(self.content) + self.raw_content = final_content[final_content.find('\0') + 1:] + self.objects = re.findall('(\d+) (\S+)\0(.{20})', self.raw_content, re.S) + + else: + raw_content = '' + for arg in args: + raw_content += '%04o %s\0%s' % (arg['mode'], arg['name'], binascii.unhexlify(arg['sha1'])) + final_content = 'tree %d\0%s' % (len(raw_content), raw_content) + super(Tree, self).__init__(final_content) + + def parse_objects(self): + res = {} + queue = list(self.objects) + while queue: + object = queue.pop(0) + mode, name, sha1 = object[0], object[1], binascii.hexlify(object[2]) + if stat.S_ISDIR(int(mode, 8)): + new_objects = Tree(sha1=sha1).objects + for new_object in new_objects: + queue.append([new_object[0], os.path.join(name, new_object[1]), new_object[2]]) + else: + res[name] = {'mode' : mode, 'sha1' : sha1} + return res class Commit(BaseObject): - def __init__(self, workspace, **kwargs): + def __init__(self, **kwargs): if kwargs['sha1']: - super(Commit, self).__init__(workspace, sha1=kwargs['sha1']) + super(Commit, self).__init__(sha1=kwargs['sha1']) final_content = zlib.decompress(self.content) res = re.findall('parent (\w+)\n', final_content) self.parent_sha1 = res[0] if res else None self.raw_content = final_content[final_content.find('tree'):] + self.tree = re.findall('tree (\w+)\n', final_content)[0] else: raw_content = 'tree %s\n' % (kwargs['tree_sha1']) @@ -63,24 +87,6 @@ def __init__(self, workspace, **kwargs): kwargs['name'], kwargs['email'], kwargs['timestamp'], kwargs['timezone'] , kwargs['msg']) final_content = 'commit %d\0%s' % (len(raw_content), raw_content) - super(Commit, self).__init__(workspace, final_content) - - - - - - - - - - - - - - - - - - + super(Commit, self).__init__(final_content) \ No newline at end of file diff --git a/src/repository.py b/src/repository.py index 2dcdff5..9d795d1 100644 --- a/src/repository.py +++ b/src/repository.py @@ -6,60 +6,39 @@ import os import time +from termcolor import colored + from config import Config +from constants import INDEX_PATH, GIT_DIR, HEAD_PATH, INIT_DIR, \ + INIT_FILE, CONFIG_PATH, REF_HEADS_DIR from index import Index -from objects import Blob, Commit +from objects import Blob, Commit, Tree from utils import read_file, write_to_file, cal_mode, write_object_to_file, \ - less_str + less_str, get_all_files_in_dir, get_file_mode class Repository(object): ''' The git repository ''' - - GIT_DIR = '.git' - - INIT_DIR = [ - 'branches', - 'hooks', - 'info', - 'objects', - 'objects/info', - 'objects/pack', - 'refs', - 'refs/heads', - 'refs/tags', - ] - - INIT_FILE = [ - ['HEAD', 'ref: refs/heads/master'], - ['description', 'Unnamed repository'], - ['info/exclude', ''], - ] - - - def __init__(self, workspace): - self.workspace = workspace - self.index = Index(os.path.join(workspace, '.git', 'index')) - self.config = Config(workspace) - self.head_path = self._get_head_path() - self.head_tree = None - if os.path.exists(self.head_path): - self.head_tree = read_file(self.head_path).strip() - - def _get_head_path(self): - branch_name = read_file(os.path.join(self.workspace, '.git', 'HEAD')).strip('\n').rsplit('/', 1)[-1] - return os.path.join(self.workspace, '.git', 'refs', 'heads', branch_name) + def __init__(self): + self.index = Index(INDEX_PATH) + self.all_files = get_all_files_in_dir('.', GIT_DIR) + self.config = Config() + self.branch_name = read_file(HEAD_PATH).strip('\n').rsplit('/', 1)[-1] + self.head_branch = os.path.join(REF_HEADS_DIR, self.branch_name) + self.head_commit = None + if os.path.exists(self.head_branch): + self.head_commit = read_file(self.head_branch).strip() def stage(self, files): try: for file in files: content = read_file(file) - blob = Blob(self.workspace, content) + blob = Blob(content) if not os.path.exists(blob.path): write_object_to_file(blob.path, blob.content) - stat = os.stat(os.path.join(self.workspace, file)) + stat = os.stat(os.path.join(file)) self.index.add_entry(file, ctime=stat.st_ctime, mtime=stat.st_mtime, dev=stat.st_dev, ino=stat.st_ino, mode=cal_mode(stat.st_mode), \ uid=stat.st_uid, gid=stat.st_gid, size=stat.st_size,sha1=blob.sha1, flags=0) self.index.write_to_file() @@ -74,13 +53,12 @@ def create_repository(workspace, bare=False): os.chdir(workspace) if not bare: - os.mkdir(Repository.GIT_DIR) - os.chdir(Repository.GIT_DIR) + os.mkdir(GIT_DIR) - for new_dir in Repository.INIT_DIR: + for new_dir in INIT_DIR: os.mkdir(new_dir) - for file_and_content in Repository.INIT_FILE: + for file_and_content in INIT_FILE: file_name = file_and_content[0] content = file_and_content[1] write_to_file(file_name, content) @@ -96,42 +74,90 @@ def create_repository(workspace, bare=False): } content = Config.create_config(init_config_dict) - write_to_file('config', content) + write_to_file(CONFIG_PATH, content) def commit(self, msg): - new_tree = self.index.do_commit(self.workspace) + new_tree = self.index.do_commit() committer_name = self.config.config_dict['user']['name'] committer_email = '<%s>' % (self.config.config_dict['user']['email']) commit_time = int(time.time()) commit_timezone = time.strftime("%z", time.gmtime()) - commit = Commit(self.workspace, sha1=None, tree_sha1=new_tree.sha1, parent_sha1=self.head_tree, name=committer_name, email=committer_email, \ + commit = Commit(sha1=None, tree_sha1=new_tree.sha1, parent_sha1=self.head_commit, name=committer_name, email=committer_email, \ timestamp=commit_time, timezone=commit_timezone, msg=msg) write_object_to_file(commit.path, commit.content) - write_to_file(self.head_path, commit.sha1) + write_to_file(self.head_branch, commit.sha1) def delete(self, file): del self.index.entries[file] self.index.write_to_file() def show_log(self, num): - cur_commit = Commit(self.workspace, sha1=self.head_tree) + cur_commit = Commit(sha1=self.head_commit) print_str = cur_commit.raw_content while num > 1 and cur_commit.parent_sha1: num -= 1 - parent_commit = Commit(self.workspace, sha1=cur_commit.parent_sha1) + parent_commit = Commit(sha1=cur_commit.parent_sha1) print_str += '\n%s' % (parent_commit.raw_content) cur_commit = parent_commit less_str(print_str) + + def _get_untracked_files(self): + return list(set(self.all_files).difference(set(list(self.index.entries)))) + + def _get_unstaged_files(self): + res = { + 'modified': [], + 'deleted' : [], + } + for name, properties in self.index.entries.iteritems(): + if name not in self.all_files: + res['deleted'].append(name) + elif get_file_mode(name) != properties['mode'] or Blob(read_file(name)).sha1 != properties['sha1']: + res['modified'].append(name) + + return res + + def _get_uncommitted_files(self): + if not self.head_commit: + return {} + tree = Tree(sha1=Commit(sha1=self.head_commit).tree) + tree_objects = tree.parse_objects() + return { + 'modified': [name for name in set(self.index.entries).intersection(set(tree_objects)) \ + if self.index.entries[name]['sha1'] != tree_objects[name]['sha1'] or \ + int(oct(self.index.entries[name]['mode'])) != int(tree_objects[name]['mode'])], + 'deleted' : set(tree_objects).difference(self.index.entries), + 'new file' : set(self.index.entries).difference(set(tree_objects)), + } + def show_status(self): + untracked_files = self._get_untracked_files() + unstaged_files = self._get_unstaged_files() + uncommitted_files = self._get_uncommitted_files() + print_str = 'On branch %s\n' % (self.branch_name) + if uncommitted_files: + print_str += 'Changes to be committed:\n (use "git reset HEAD ..." to unstage)\n\n' + for change, files in uncommitted_files.iteritems(): + for file in files: + print_str += colored('\t%s:\t%s\n' % (change, file), 'green') + print_str += '\n' + if unstaged_files: + print_str += 'Changes not staged for commit:\n (use "git add ..." to update what will be committed)\n' + print_str += ' (use "git checkout -- ..." to discard changes in working directory)\n\n' + for change, files in unstaged_files.iteritems(): + for file in files: + print_str += colored('\t%s:\t%s\n' % (change, file), 'red') + print_str += '\n' + if untracked_files: + print_str += 'Untracked files:\n (use "git add ..." to include in what will be committed)\n\n' + for file in untracked_files: + print_str += colored('\t%s\n' % file, 'red') + print_str += '\n' - - - - - \ No newline at end of file + print print_str \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index f046327..177e5bb 100644 --- a/src/utils.py +++ b/src/utils.py @@ -42,12 +42,25 @@ def cal_mode(mode): ret |= (mode & 0o111) return ret +def get_file_mode(path): + res = os.stat(path) + return cal_mode(res.st_mode) + def less_str(str): with tempfile.NamedTemporaryFile() as f: f.write(str) f.seek(0) os.system("cat %s | less" % f.name) +def get_all_files_in_dir(dir, *exclude_dirs): + file_list = [] + for root, dirs, files in os.walk(dir): + for exclude_dir in set(exclude_dirs).intersection(set(dirs)): + dirs.remove(exclude_dir) + for file in files: + file_list.append(os.path.join(root[2:], file)) + return file_list + class Sha1Reader(object): def __init__(self, f): From aa62c357bc4d5a018bb3e6cec38983668de7b893 Mon Sep 17 00:00:00 2001 From: lizherui Date: Sun, 22 Jun 2014 02:15:01 +0800 Subject: [PATCH 07/29] update schedule --- Schedule.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Schedule.md b/Schedule.md index 642b6b8..bd085a9 100644 --- a/Schedule.md +++ b/Schedule.md @@ -4,6 +4,7 @@ * commit * rm * log +* status ###Doing * push From 370a3c40bce7e6e3010d989382e7957641808d54 Mon Sep 17 00:00:00 2001 From: lizherui Date: Sun, 22 Jun 2014 11:28:39 +0800 Subject: [PATCH 08/29] fix status bug --- src/repository.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/repository.py b/src/repository.py index 9d795d1..7ce2c7b 100644 --- a/src/repository.py +++ b/src/repository.py @@ -139,25 +139,23 @@ def show_status(self): unstaged_files = self._get_unstaged_files() uncommitted_files = self._get_uncommitted_files() print_str = 'On branch %s\n' % (self.branch_name) - if uncommitted_files: - print_str += 'Changes to be committed:\n (use "git reset HEAD ..." to unstage)\n\n' - for change, files in uncommitted_files.iteritems(): - for file in files: - print_str += colored('\t%s:\t%s\n' % (change, file), 'green') - print_str += '\n' - if unstaged_files: - print_str += 'Changes not staged for commit:\n (use "git add ..." to update what will be committed)\n' - print_str += ' (use "git checkout -- ..." to discard changes in working directory)\n\n' - for change, files in unstaged_files.iteritems(): - for file in files: - print_str += colored('\t%s:\t%s\n' % (change, file), 'red') - print_str += '\n' + print_str += 'Changes to be committed:\n (use "git reset HEAD ..." to unstage)\n\n' + for change, files in uncommitted_files.iteritems(): + for file in files: + print_str += colored('\t%s:\t%s\n' % (change, file), 'green') + print_str += '\n' + + print_str += 'Changes not staged for commit:\n (use "git add ..." to update what will be committed)\n' + print_str += ' (use "git checkout -- ..." to discard changes in working directory)\n\n' + for change, files in unstaged_files.iteritems(): + for file in files: + print_str += colored('\t%s:\t%s\n' % (change, file), 'red') + print_str += '\n' - if untracked_files: - print_str += 'Untracked files:\n (use "git add ..." to include in what will be committed)\n\n' - for file in untracked_files: - print_str += colored('\t%s\n' % file, 'red') - print_str += '\n' + print_str += 'Untracked files:\n (use "git add ..." to include in what will be committed)\n\n' + for file in untracked_files: + print_str += colored('\t%s\n' % file, 'red') + print_str += '\n' print print_str \ No newline at end of file From e8ca1c0b7013add890ea70f7796cebfa69a59158 Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 16:01:27 +0800 Subject: [PATCH 09/29] add .gitignore support --- Requirements | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Requirements diff --git a/Requirements b/Requirements new file mode 100644 index 0000000..34bc775 --- /dev/null +++ b/Requirements @@ -0,0 +1,2 @@ +termcolor >= 1.1.0 +pathspec >= 0.2.2 From 03f4aa40d7379a186cf987f7b69d4f0c1bb25fd6 Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 16:01:34 +0800 Subject: [PATCH 10/29] add .gitignore support --- .gitignore | 2 +- Schedule.md | 1 + src/command.py | 4 +-- src/constants.py | 2 ++ src/repository.py | 75 ++++++++++++++++++++++++----------------------- src/utils.py | 9 ++++++ 6 files changed, 53 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index d15fa18..33337d1 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ coverage.xml .mr.developer.cfg .project .pydevproject -.settings +.settings/ # Rope .ropeproject diff --git a/Schedule.md b/Schedule.md index bd085a9..6197f0a 100644 --- a/Schedule.md +++ b/Schedule.md @@ -5,6 +5,7 @@ * rm * log * status +* .gitignore ###Doing * push diff --git a/src/command.py b/src/command.py index fd19569..2eb8125 100644 --- a/src/command.py +++ b/src/command.py @@ -7,7 +7,7 @@ from constants import GIT_DIR from repository import Repository -from utils import get_all_files_in_dir +from utils import get_all_files_in_dir, filter_by_gitignore class Command(object): @@ -23,7 +23,7 @@ def cmd_init(workspace, bare): @staticmethod def cmd_add(file): if file == '.': - Repository().stage(get_all_files_in_dir('.', GIT_DIR)) + Repository().stage(filter_by_gitignore(get_all_files_in_dir('.', GIT_DIR))) else: Repository().stage([file]) diff --git a/src/constants.py b/src/constants.py index c085991..b4a013f 100644 --- a/src/constants.py +++ b/src/constants.py @@ -14,6 +14,8 @@ CONFIG_PATH = os.path.join(GIT_DIR, 'config') +GITIGNORE_PATH = '.gitignore' + DESCRIPTION_PATH = os.path.join(GIT_DIR, 'description') BRANCHES_DIR = os.path.join(GIT_DIR, 'branches') diff --git a/src/repository.py b/src/repository.py index 7ce2c7b..18a990b 100644 --- a/src/repository.py +++ b/src/repository.py @@ -9,12 +9,12 @@ from termcolor import colored from config import Config -from constants import INDEX_PATH, GIT_DIR, HEAD_PATH, INIT_DIR, \ - INIT_FILE, CONFIG_PATH, REF_HEADS_DIR +from constants import INDEX_PATH, GIT_DIR, HEAD_PATH, REF_HEADS_DIR, INIT_DIR, \ + INIT_FILE, CONFIG_PATH from index import Index from objects import Blob, Commit, Tree from utils import read_file, write_to_file, cal_mode, write_object_to_file, \ - less_str, get_all_files_in_dir, get_file_mode + less_str, get_all_files_in_dir, get_file_mode, filter_by_gitignore class Repository(object): @@ -30,7 +30,7 @@ def __init__(self): self.head_commit = None if os.path.exists(self.head_branch): self.head_commit = read_file(self.head_branch).strip() - + def stage(self, files): try: for file in files: @@ -40,30 +40,30 @@ def stage(self, files): write_object_to_file(blob.path, blob.content) stat = os.stat(os.path.join(file)) self.index.add_entry(file, ctime=stat.st_ctime, mtime=stat.st_mtime, dev=stat.st_dev, ino=stat.st_ino, mode=cal_mode(stat.st_mode), \ - uid=stat.st_uid, gid=stat.st_gid, size=stat.st_size,sha1=blob.sha1, flags=0) + uid=stat.st_uid, gid=stat.st_gid, size=stat.st_size, sha1=blob.sha1, flags=0) self.index.write_to_file() - + except Exception, e: print 'stage file %s error: %s' % (file, e) - - @staticmethod + + @staticmethod def create_repository(workspace, bare=False): - if not os.path.exists(workspace): + if not os.path.exists(workspace): os.mkdir(workspace) os.chdir(workspace) - + if not bare: os.mkdir(GIT_DIR) - + for new_dir in INIT_DIR: os.mkdir(new_dir) - + for file_and_content in INIT_FILE: - file_name = file_and_content[0] + file_name = file_and_content[0] content = file_and_content[1] write_to_file(file_name, content) - - + + init_config_dict = { 'core': { 'repositoryformatversion' : '0', @@ -72,40 +72,41 @@ def create_repository(workspace, bare=False): 'logallrefupdates' : 'true', } } - + content = Config.create_config(init_config_dict) write_to_file(CONFIG_PATH, content) - + def commit(self, msg): new_tree = self.index.do_commit() - + committer_name = self.config.config_dict['user']['name'] - committer_email = '<%s>' % (self.config.config_dict['user']['email']) + committer_email = '<%s>' % (self.config.config_dict['user']['email']) commit_time = int(time.time()) commit_timezone = time.strftime("%z", time.gmtime()) - + commit = Commit(sha1=None, tree_sha1=new_tree.sha1, parent_sha1=self.head_commit, name=committer_name, email=committer_email, \ timestamp=commit_time, timezone=commit_timezone, msg=msg) write_object_to_file(commit.path, commit.content) write_to_file(self.head_branch, commit.sha1) - + def delete(self, file): del self.index.entries[file] self.index.write_to_file() - + def show_log(self, num): cur_commit = Commit(sha1=self.head_commit) - print_str = cur_commit.raw_content + print_str = cur_commit.raw_content while num > 1 and cur_commit.parent_sha1: num -= 1 parent_commit = Commit(sha1=cur_commit.parent_sha1) - print_str += '\n%s' % (parent_commit.raw_content) + print_str += '\n%s' % (parent_commit.raw_content) cur_commit = parent_commit less_str(print_str) - + def _get_untracked_files(self): - return list(set(self.all_files).difference(set(list(self.index.entries)))) - + raw_list = list(set(self.all_files).difference(set(list(self.index.entries)))) + return filter_by_gitignore(raw_list) + def _get_unstaged_files(self): res = { 'modified': [], @@ -116,14 +117,14 @@ def _get_unstaged_files(self): res['deleted'].append(name) elif get_file_mode(name) != properties['mode'] or Blob(read_file(name)).sha1 != properties['sha1']: res['modified'].append(name) - + return res - - + + def _get_uncommitted_files(self): if not self.head_commit: return {} - + tree = Tree(sha1=Commit(sha1=self.head_commit).tree) tree_objects = tree.parse_objects() return { @@ -133,29 +134,29 @@ def _get_uncommitted_files(self): 'deleted' : set(tree_objects).difference(self.index.entries), 'new file' : set(self.index.entries).difference(set(tree_objects)), } - + def show_status(self): untracked_files = self._get_untracked_files() unstaged_files = self._get_unstaged_files() uncommitted_files = self._get_uncommitted_files() print_str = 'On branch %s\n' % (self.branch_name) - + print_str += 'Changes to be committed:\n (use "git reset HEAD ..." to unstage)\n\n' for change, files in uncommitted_files.iteritems(): for file in files: print_str += colored('\t%s:\t%s\n' % (change, file), 'green') print_str += '\n' - + print_str += 'Changes not staged for commit:\n (use "git add ..." to update what will be committed)\n' print_str += ' (use "git checkout -- ..." to discard changes in working directory)\n\n' for change, files in unstaged_files.iteritems(): for file in files: print_str += colored('\t%s:\t%s\n' % (change, file), 'red') print_str += '\n' - + print_str += 'Untracked files:\n (use "git add ..." to include in what will be committed)\n\n' for file in untracked_files: print_str += colored('\t%s\n' % file, 'red') print_str += '\n' - - print print_str \ No newline at end of file + + print print_str diff --git a/src/utils.py b/src/utils.py index 177e5bb..62e51f0 100644 --- a/src/utils.py +++ b/src/utils.py @@ -9,6 +9,10 @@ import stat import tempfile +import pathspec + +from constants import GITIGNORE_PATH + S_IFGITLINK = 0o160000 @@ -61,6 +65,11 @@ def get_all_files_in_dir(dir, *exclude_dirs): file_list.append(os.path.join(root[2:], file)) return file_list +def filter_by_gitignore(raw_list): + with open(GITIGNORE_PATH, 'r') as fh: + spec = pathspec.PathSpec.from_lines(pathspec.GitIgnorePattern, fh) + return set(raw_list).difference(spec.match_files(raw_list)) + class Sha1Reader(object): def __init__(self, f): From 05478ffe8978dfc0517e9ba94307981efcef0872 Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 16:48:51 +0800 Subject: [PATCH 11/29] update README --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fe9199f..a0609a8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ This is the git-in-python project. It aims to rewrite Git in Python, perhaps with some C code for high performance. -The file named 'git.py' is the entrance of the whole project. +'git.py' is the entrance of the whole project. + +Please read 'CodingStyle.md', 'Schedule.md' before contributing and 'Requirements' before running. ##Why [The Official Git](https://github.com/git/git) written in C attracts hackers all over the world since created. @@ -18,7 +20,10 @@ So, curiosity drives me to look inside [Git](https://github.com/git/git) and rew This project takes me a lot of time. I have to work for company during the day so that I can only dev this project during a few hours at night. -##Requirement +##Target +Dev the core command of the official git such as 'init', 'add', 'commit', 'push', 'clone' that when we run git.py xxx, the result is the same as git xxx. Otherwise, there is something wrong maybe. + +##Contribute Rewrite Git in Python seems not something easy, so this project is not for C/Python/Git beginners. However, don't get frustrated, It's not that hard.You can contribute to this project step by step: @@ -30,9 +35,6 @@ However, don't get frustrated, It's not that hard.You can contribute to this pro 5. understand Git source code: [the official Git source code](https://github.com/git/git) 6. fork this project, fix bugs, add features, and even rebuild the architecture. -##Target -Dev the core command of the official git such as 'init', 'add', 'commit', 'push', 'clone' that when we run git.py xxx, the result is the same as git xxx. Otherwise, there is something wrong maybe. - ##Future Surely, It takes time, It takes patients. From 910bd2853cfbec641dffc7e46d2233b633bfaf33 Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 16:54:31 +0800 Subject: [PATCH 12/29] fix gitignore not exists bug --- src/utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utils.py b/src/utils.py index 62e51f0..5516064 100644 --- a/src/utils.py +++ b/src/utils.py @@ -66,9 +66,12 @@ def get_all_files_in_dir(dir, *exclude_dirs): return file_list def filter_by_gitignore(raw_list): - with open(GITIGNORE_PATH, 'r') as fh: - spec = pathspec.PathSpec.from_lines(pathspec.GitIgnorePattern, fh) - return set(raw_list).difference(spec.match_files(raw_list)) + if not os.path.exists(GITIGNORE_PATH): + return raw_list + else: + with open(GITIGNORE_PATH, 'r') as fh: + spec = pathspec.PathSpec.from_lines(pathspec.GitIgnorePattern, fh) + return set(raw_list).difference(spec.match_files(raw_list)) class Sha1Reader(object): From 897ae78770607e7eb5154af730021360b2731bc8 Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 18:19:10 +0800 Subject: [PATCH 13/29] add branch command --- README.md | 2 +- Schedule.md | 3 ++- src/branch.py | 43 +++++++++++++++++++++++++++++++++++++++++++ src/command.py | 13 +++++++++++++ src/git.py | 27 +++++++++++++++++++++++++++ src/repository.py | 21 +++++++++------------ 6 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 src/branch.py diff --git a/README.md b/README.md index a0609a8..83900fe 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This project takes me a lot of time. I have to work for company during the day s ##Target Dev the core command of the official git such as 'init', 'add', 'commit', 'push', 'clone' that when we run git.py xxx, the result is the same as git xxx. Otherwise, there is something wrong maybe. -##Contribute +##Contribution Rewrite Git in Python seems not something easy, so this project is not for C/Python/Git beginners. However, don't get frustrated, It's not that hard.You can contribute to this project step by step: diff --git a/Schedule.md b/Schedule.md index 6197f0a..1f92abc 100644 --- a/Schedule.md +++ b/Schedule.md @@ -6,7 +6,8 @@ * log * status * .gitignore - +* branch +* ###Doing * push diff --git a/src/branch.py b/src/branch.py new file mode 100644 index 0000000..f10012d --- /dev/null +++ b/src/branch.py @@ -0,0 +1,43 @@ +''' +Created on Jun 24, 2014 + +@author: lzrak47 +''' +import os +import shutil + +from constants import HEAD_PATH, REF_HEADS_DIR +from utils import read_file, get_all_files_in_dir + + +class Branch(object): + ''' + git branch + ''' + + def __init__(self): + ''' + Constructor + ''' + self.head_name = read_file(HEAD_PATH).strip('\n').rsplit('/', 1)[-1] + self.head_path = os.path.join(REF_HEADS_DIR, self.head_name) + self.head_commit = read_file(self.head_path).strip() if os.path.exists(self.head_path) else None + + def get_all_branches(self): + return os.listdir(REF_HEADS_DIR) + + def _check_branch_exists(self, name): + return os.path.exists(os.path.join(REF_HEADS_DIR, name)) + + def add_branch(self, name): + if self._check_branch_exists(name): + print "fatal: A branch named '%s' already exists." % (name) + exit(1) + shutil.copyfile(self.head_path, os.path.join(REF_HEADS_DIR, name)) + + def delete_branch(self, name): + if self.head_name == name: + print "error: Cannot delete the branch '%s' which you are currently on." % (name) + exit(1) + os.remove(os.path.join(REF_HEADS_DIR, name)) + \ No newline at end of file diff --git a/src/command.py b/src/command.py index 2eb8125..039d74b 100644 --- a/src/command.py +++ b/src/command.py @@ -5,6 +5,8 @@ ''' import os +from termcolor import colored + from constants import GIT_DIR from repository import Repository from utils import get_all_files_in_dir, filter_by_gitignore @@ -45,6 +47,17 @@ def cmd_log(num): def cmd_status(): Repository().show_status() + @staticmethod + def cmd_branch(name, is_deleted): + repo = Repository() + if not name: + for branch in repo.branch.get_all_branches(): + print '* %s' % colored(branch, 'green') if branch == repo.branch.head_name else ' %s' % branch + elif is_deleted: + repo.branch.delete_branch(name) + else : + repo.branch.add_branch(name) + @staticmethod def cmd_push(): pass diff --git a/src/git.py b/src/git.py index 8a94156..6836c38 100755 --- a/src/git.py +++ b/src/git.py @@ -121,6 +121,30 @@ def __init__(self, argv): 'args' : [], }, + 'branch' : { + 'func' : self._branch, + 'help' : 'List, create, or delete branches', + 'args' : [ + { + 'name' : ['name'], + 'properties' : + { + 'help' : 'The name of the branch to create or delete', + 'nargs' : '?', + 'default' : '', + } + }, + { + 'name' : ['-d'], + 'properties' : + { + 'help' : 'Delete a branch.', + 'action' : 'store_true', + 'dest' : 'is_deleted', + } + } + ], + }, 'push' : { 'func' : self._push, 'help' : 'Update remote refs along with associated objects', @@ -153,6 +177,9 @@ def _log(self, args): def _status(self, args): Command.cmd_status() + def _branch(self, args): + Command.cmd_branch(args.name, args.is_deleted) + def _push(self, args): pass diff --git a/src/repository.py b/src/repository.py index 18a990b..e3fbd4e 100644 --- a/src/repository.py +++ b/src/repository.py @@ -8,8 +8,9 @@ from termcolor import colored +from branch import Branch from config import Config -from constants import INDEX_PATH, GIT_DIR, HEAD_PATH, REF_HEADS_DIR, INIT_DIR, \ +from constants import INDEX_PATH, GIT_DIR, INIT_DIR, \ INIT_FILE, CONFIG_PATH from index import Index from objects import Blob, Commit, Tree @@ -25,11 +26,7 @@ def __init__(self): self.index = Index(INDEX_PATH) self.all_files = get_all_files_in_dir('.', GIT_DIR) self.config = Config() - self.branch_name = read_file(HEAD_PATH).strip('\n').rsplit('/', 1)[-1] - self.head_branch = os.path.join(REF_HEADS_DIR, self.branch_name) - self.head_commit = None - if os.path.exists(self.head_branch): - self.head_commit = read_file(self.head_branch).strip() + self.branch = Branch() def stage(self, files): try: @@ -84,17 +81,17 @@ def commit(self, msg): commit_time = int(time.time()) commit_timezone = time.strftime("%z", time.gmtime()) - commit = Commit(sha1=None, tree_sha1=new_tree.sha1, parent_sha1=self.head_commit, name=committer_name, email=committer_email, \ + commit = Commit(sha1=None, tree_sha1=new_tree.sha1, parent_sha1=self.branch.head_commit, name=committer_name, email=committer_email, \ timestamp=commit_time, timezone=commit_timezone, msg=msg) write_object_to_file(commit.path, commit.content) - write_to_file(self.head_branch, commit.sha1) + write_to_file(self.branch.head_path, commit.sha1) def delete(self, file): del self.index.entries[file] self.index.write_to_file() def show_log(self, num): - cur_commit = Commit(sha1=self.head_commit) + cur_commit = Commit(sha1=self.branch.head_commit) print_str = cur_commit.raw_content while num > 1 and cur_commit.parent_sha1: num -= 1 @@ -122,10 +119,10 @@ def _get_unstaged_files(self): def _get_uncommitted_files(self): - if not self.head_commit: + if not self.branch.head_commit: return {} - tree = Tree(sha1=Commit(sha1=self.head_commit).tree) + tree = Tree(sha1=Commit(sha1=self.branch.head_commit).tree) tree_objects = tree.parse_objects() return { 'modified': [name for name in set(self.index.entries).intersection(set(tree_objects)) \ @@ -139,7 +136,7 @@ def show_status(self): untracked_files = self._get_untracked_files() unstaged_files = self._get_unstaged_files() uncommitted_files = self._get_uncommitted_files() - print_str = 'On branch %s\n' % (self.branch_name) + print_str = 'On branch %s\n' % (self.branch.head_name) print_str += 'Changes to be committed:\n (use "git reset HEAD ..." to unstage)\n\n' for change, files in uncommitted_files.iteritems(): From ef4fcaf65562774c7c8ef28df9460c779e17b3b8 Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 18:22:50 +0800 Subject: [PATCH 14/29] update documents --- README.md | 4 ++-- Schedule.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 83900fe..a27bfda 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ This is the git-in-python project. It aims to rewrite Git in Python, perhaps with some C code for high performance. -'git.py' is the entrance of the whole project. +[git.py](https://github.com/lizherui/git-in-python/blob/master/src/git.py) is the entrance of the whole project. -Please read 'CodingStyle.md', 'Schedule.md' before contributing and 'Requirements' before running. +Please read [CodingStyle](https://github.com/lizherui/git-in-python/blob/master/CodingStyle.md), [Schedule](https://github.com/lizherui/git-in-python/blob/master/Schedule.md) before contributing and [Requirements](https://github.com/lizherui/git-in-python/blob/master/Requirements) before running. ##Why [The Official Git](https://github.com/git/git) written in C attracts hackers all over the world since created. diff --git a/Schedule.md b/Schedule.md index 1f92abc..6785ff9 100644 --- a/Schedule.md +++ b/Schedule.md @@ -7,7 +7,7 @@ * status * .gitignore * branch -* + ###Doing * push From 54cc53e57ae8c66d63279aa153c4862067484e9e Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 18:24:25 +0800 Subject: [PATCH 15/29] update documents --- Schedule.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Schedule.md b/Schedule.md index 6785ff9..db45fc9 100644 --- a/Schedule.md +++ b/Schedule.md @@ -4,8 +4,7 @@ * commit * rm * log -* status -* .gitignore +* status(with .gitignore support) * branch ###Doing From d8dc03a766870e1192233036c19456dc86fc66fb Mon Sep 17 00:00:00 2001 From: lizherui Date: Tue, 24 Jun 2014 18:28:32 +0800 Subject: [PATCH 16/29] rename Requirements --- README.md | 2 +- Requirements => requirements.txt | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Requirements => requirements.txt (100%) diff --git a/README.md b/README.md index a27bfda..16df005 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ It aims to rewrite Git in Python, perhaps with some C code for high performance. [git.py](https://github.com/lizherui/git-in-python/blob/master/src/git.py) is the entrance of the whole project. -Please read [CodingStyle](https://github.com/lizherui/git-in-python/blob/master/CodingStyle.md), [Schedule](https://github.com/lizherui/git-in-python/blob/master/Schedule.md) before contributing and [Requirements](https://github.com/lizherui/git-in-python/blob/master/Requirements) before running. +Please read the [CodingStyle](https://github.com/lizherui/git-in-python/blob/master/CodingStyle.md) and [Schedule](https://github.com/lizherui/git-in-python/blob/master/Schedule.md) before contributing, pip install -r [requirements.txt](https://github.com/lizherui/git-in-python/blob/master/requirements.txt) before running. ##Why [The Official Git](https://github.com/git/git) written in C attracts hackers all over the world since created. diff --git a/Requirements b/requirements.txt similarity index 100% rename from Requirements rename to requirements.txt From 3c2dea3fd87ef6e5408035ee83b19dc87a22faca Mon Sep 17 00:00:00 2001 From: lizherui Date: Wed, 25 Jun 2014 20:43:40 +0800 Subject: [PATCH 17/29] add reset command --- Schedule.md | 6 ++-- src/branch.py | 2 +- src/command.py | 9 +++++ src/git.py | 32 +++++++++++++++++ src/index.py | 8 ++--- src/objects.py | 14 +++++--- src/repository.py | 92 ++++++++++++++++++++++++++++++++--------------- src/utils.py | 16 +++++---- 8 files changed, 132 insertions(+), 47 deletions(-) diff --git a/Schedule.md b/Schedule.md index db45fc9..b553d2c 100644 --- a/Schedule.md +++ b/Schedule.md @@ -4,8 +4,9 @@ * commit * rm * log -* status(with .gitignore support) +* status * branch +* reset ###Doing * push @@ -14,5 +15,4 @@ * clone * diff * merge - -...... +* ...... diff --git a/src/branch.py b/src/branch.py index f10012d..8cc899e 100644 --- a/src/branch.py +++ b/src/branch.py @@ -7,7 +7,7 @@ import shutil from constants import HEAD_PATH, REF_HEADS_DIR -from utils import read_file, get_all_files_in_dir +from utils import read_file class Branch(object): diff --git a/src/command.py b/src/command.py index 039d74b..be26271 100644 --- a/src/command.py +++ b/src/command.py @@ -58,6 +58,15 @@ def cmd_branch(name, is_deleted): else : repo.branch.add_branch(name) + @staticmethod + def cmd_reset(commit_sha1, is_soft, is_hard): + repo = Repository() + repo.update_head_commit(commit_sha1) + if not is_soft: + repo.rebuild_index_from_commit(commit_sha1) + if is_hard: + repo.rebuild_working_tree() + @staticmethod def cmd_push(): pass diff --git a/src/git.py b/src/git.py index 6836c38..2bde96c 100755 --- a/src/git.py +++ b/src/git.py @@ -145,6 +145,35 @@ def __init__(self, argv): } ], }, + 'reset' : { + 'func' : self._reset, + 'help' : 'Reset current HEAD to the specified state', + 'args' : [ + { + 'name' : ['commit_sha1'], + 'properties' : + { + 'help' : 'Sha1 of a commit', + } + }, + { + 'name' : ['--soft'], + 'properties' : + { + 'help' : 'Does not touch the index file or the working tree at all', + 'action' : 'store_true', + } + }, + { + 'name' : ['--hard'], + 'properties' : + { + 'help' : 'Resets the index and working tree', + 'action' : 'store_true', + } + }, + ], + }, 'push' : { 'func' : self._push, 'help' : 'Update remote refs along with associated objects', @@ -180,6 +209,9 @@ def _status(self, args): def _branch(self, args): Command.cmd_branch(args.name, args.is_deleted) + def _reset(self, args): + Command.cmd_reset(args.commit_sha1, is_soft=args.soft, is_hard=args.hard) + def _push(self, args): pass diff --git a/src/index.py b/src/index.py index acd61e2..2a73007 100644 --- a/src/index.py +++ b/src/index.py @@ -10,7 +10,7 @@ import struct from objects import Tree -from utils import Sha1Reader, Sha1Writer, write_object_to_file +from utils import Sha1Reader, Sha1Writer, write_to_file class Index(object): @@ -33,7 +33,7 @@ def _parse_header(self, f): return entries_num - def add_entry(self, name, **kwargs): + def set_entry(self, name, **kwargs): self.entries[name] = kwargs def _parse_entries(self, f): @@ -46,7 +46,7 @@ def _parse_entries(self, f): real_size = ((f.tell() - begin + 8) & ~7) f.read((begin + real_size) - f.tell()) - self.add_entry(name, ctime=ctime, mtime=mtime, dev=dev, ino=ino, mode=mode, \ + self.set_entry(name, ctime=ctime, mtime=mtime, dev=dev, ino=ino, mode=int(mode), \ uid=uid, gid=gid, size=size,sha1=binascii.hexlify(sha1), flags=flags & ~0x0fff) def _parse_file(self): @@ -116,7 +116,7 @@ def _build_tree(path): (mode, sha1) = entry file_arr.append({'name':name, 'mode':mode, 'sha1':sha1}) newtree = Tree(sorted(dir_arr,key = lambda x:x['name']) + sorted(file_arr,key = lambda x:x['name'])) - write_object_to_file(newtree.path, newtree.content) + write_to_file(newtree.path, newtree.content) return newtree return _build_tree(tree) diff --git a/src/objects.py b/src/objects.py index 21b171d..a52c9e5 100644 --- a/src/objects.py +++ b/src/objects.py @@ -14,6 +14,7 @@ from utils import cal_sha1, read_file + class BaseObject(object): ''' git base object @@ -34,8 +35,13 @@ def __init__(self, final_content=None, sha1=None): class Blob(BaseObject): def __init__(self, raw_content=None, sha1=None): - final_content = 'blob %d\0%s' % (len(raw_content), raw_content) - super(Blob, self).__init__(final_content) + if sha1: + super(Blob, self).__init__(sha1=sha1) + final_content = zlib.decompress(self.content) + self.raw_content = final_content[final_content.find('\0') + 1:] + else: + final_content = 'blob %d\0%s' % (len(raw_content), raw_content) + super(Blob, self).__init__(final_content) class Tree(BaseObject): def __init__(self, args=None, sha1=None): @@ -43,7 +49,7 @@ def __init__(self, args=None, sha1=None): super(Tree, self).__init__(sha1=sha1) final_content = zlib.decompress(self.content) self.raw_content = final_content[final_content.find('\0') + 1:] - self.objects = re.findall('(\d+) (\S+)\0(.{20})', self.raw_content, re.S) + self.objects = re.findall('(\d+) ([^\0]+)\0(.{20})', self.raw_content, re.S) else: raw_content = '' @@ -63,7 +69,7 @@ def parse_objects(self): for new_object in new_objects: queue.append([new_object[0], os.path.join(name, new_object[1]), new_object[2]]) else: - res[name] = {'mode' : mode, 'sha1' : sha1} + res[name] = {'mode' : int(mode, 8), 'sha1' : sha1} return res diff --git a/src/repository.py b/src/repository.py index e3fbd4e..c201c1d 100644 --- a/src/repository.py +++ b/src/repository.py @@ -14,8 +14,8 @@ INIT_FILE, CONFIG_PATH from index import Index from objects import Blob, Commit, Tree -from utils import read_file, write_to_file, cal_mode, write_object_to_file, \ - less_str, get_all_files_in_dir, get_file_mode, filter_by_gitignore +from utils import get_all_files_in_dir, read_file, write_to_file, cal_mode, \ + less_str, filter_by_gitignore, get_file_mode class Repository(object): @@ -24,7 +24,7 @@ class Repository(object): ''' def __init__(self): self.index = Index(INDEX_PATH) - self.all_files = get_all_files_in_dir('.', GIT_DIR) + self.working_tree_files = get_all_files_in_dir('.', GIT_DIR) self.config = Config() self.branch = Branch() @@ -34,11 +34,12 @@ def stage(self, files): content = read_file(file) blob = Blob(content) if not os.path.exists(blob.path): - write_object_to_file(blob.path, blob.content) + write_to_file(blob.path, blob.content) stat = os.stat(os.path.join(file)) - self.index.add_entry(file, ctime=stat.st_ctime, mtime=stat.st_mtime, dev=stat.st_dev, ino=stat.st_ino, mode=cal_mode(stat.st_mode), \ + self.index.set_entry(file, ctime=stat.st_ctime, mtime=stat.st_mtime, dev=stat.st_dev, ino=stat.st_ino, mode=cal_mode(stat.st_mode), \ uid=stat.st_uid, gid=stat.st_gid, size=stat.st_size, sha1=blob.sha1, flags=0) self.index.write_to_file() + self.index = Index(INDEX_PATH) except Exception, e: print 'stage file %s error: %s' % (file, e) @@ -83,7 +84,7 @@ def commit(self, msg): commit = Commit(sha1=None, tree_sha1=new_tree.sha1, parent_sha1=self.branch.head_commit, name=committer_name, email=committer_email, \ timestamp=commit_time, timezone=commit_timezone, msg=msg) - write_object_to_file(commit.path, commit.content) + write_to_file(commit.path, commit.content) write_to_file(self.branch.head_path, commit.sha1) def delete(self, file): @@ -101,7 +102,7 @@ def show_log(self, num): less_str(print_str) def _get_untracked_files(self): - raw_list = list(set(self.all_files).difference(set(list(self.index.entries)))) + raw_list = list(set(self.working_tree_files).difference(set(list(self.index.entries)))) return filter_by_gitignore(raw_list) def _get_unstaged_files(self): @@ -110,7 +111,7 @@ def _get_unstaged_files(self): 'deleted' : [], } for name, properties in self.index.entries.iteritems(): - if name not in self.all_files: + if name not in self.working_tree_files: res['deleted'].append(name) elif get_file_mode(name) != properties['mode'] or Blob(read_file(name)).sha1 != properties['sha1']: res['modified'].append(name) @@ -120,14 +121,14 @@ def _get_unstaged_files(self): def _get_uncommitted_files(self): if not self.branch.head_commit: - return {} + return {'new file' : self.index.entries} tree = Tree(sha1=Commit(sha1=self.branch.head_commit).tree) tree_objects = tree.parse_objects() return { 'modified': [name for name in set(self.index.entries).intersection(set(tree_objects)) \ if self.index.entries[name]['sha1'] != tree_objects[name]['sha1'] or \ - int(oct(self.index.entries[name]['mode'])) != int(tree_objects[name]['mode'])], + self.index.entries[name]['mode'] != tree_objects[name]['mode']], 'deleted' : set(tree_objects).difference(self.index.entries), 'new file' : set(self.index.entries).difference(set(tree_objects)), } @@ -137,23 +138,58 @@ def show_status(self): unstaged_files = self._get_unstaged_files() uncommitted_files = self._get_uncommitted_files() print_str = 'On branch %s\n' % (self.branch.head_name) - - print_str += 'Changes to be committed:\n (use "git reset HEAD ..." to unstage)\n\n' - for change, files in uncommitted_files.iteritems(): - for file in files: - print_str += colored('\t%s:\t%s\n' % (change, file), 'green') - print_str += '\n' - - print_str += 'Changes not staged for commit:\n (use "git add ..." to update what will be committed)\n' - print_str += ' (use "git checkout -- ..." to discard changes in working directory)\n\n' - for change, files in unstaged_files.iteritems(): - for file in files: - print_str += colored('\t%s:\t%s\n' % (change, file), 'red') - print_str += '\n' - - print_str += 'Untracked files:\n (use "git add ..." to include in what will be committed)\n\n' - for file in untracked_files: - print_str += colored('\t%s\n' % file, 'red') - print_str += '\n' + + if uncommitted_files['modified'] or uncommitted_files['deleted'] or uncommitted_files['new file']: + print_str += 'Changes to be committed:\n (use "git reset HEAD ..." to unstage)\n\n' + for change, files in uncommitted_files.iteritems(): + for file in files: + print_str += colored('\t%s:\t%s\n' % (change, file), 'green') + print_str += '\n' + + if unstaged_files['modified'] or unstaged_files['deleted']: + print_str += 'Changes not staged for commit:\n (use "git add ..." to update what will be committed)\n' + print_str += ' (use "git checkout -- ..." to discard changes in working directory)\n\n' + for change, files in unstaged_files.iteritems(): + for file in files: + print_str += colored('\t%s:\t%s\n' % (change, file), 'red') + print_str += '\n' + + if untracked_files: + print_str += 'Untracked files:\n (use "git add ..." to include in what will be committed)\n\n' + for file in untracked_files: + print_str += colored('\t%s\n' % file, 'red') + print_str += '\n' print print_str + + def update_head_commit(self, commit_sha1): + write_to_file(self.branch.head_path, commit_sha1) + + def rebuild_index_from_commit(self, commit_sha1): + tree = Tree(sha1=Commit(sha1=commit_sha1).tree) + tree_objects = tree.parse_objects() + + for name in set(self.index.entries).difference(set(tree_objects)): + self.index.entries.pop(name) + + for name, properties in tree_objects.iteritems(): + if not self.index.entries.has_key(name) or properties['sha1'] != self.index.entries[name]['sha1'] or \ + properties['mode'] != self.index.entries[name]['mode']: + self.index.set_entry(name, ctime=0.0, mtime=0.0, dev=0, ino=0, mode=properties['mode'], \ + uid=0, gid=0, size=0, sha1=properties['sha1'], flags=0) + + self.index.write_to_file() + self.index = Index(INDEX_PATH) + + def rebuild_working_tree(self): + for path, properties in self.index.entries.iteritems(): + content = Blob(sha1=properties['sha1']).raw_content + write_to_file(path, content, mode=properties['mode']) + + + + + + + + diff --git a/src/utils.py b/src/utils.py index 5516064..6dce514 100644 --- a/src/utils.py +++ b/src/utils.py @@ -16,15 +16,17 @@ S_IFGITLINK = 0o160000 -def write_to_file(path, content): +def write_to_file(path, content, mode=None): + dir = os.path.dirname(path) + + if dir and not os.path.exists(dir): + os.makedirs(dir) + with open(path, 'w') as f: f.write(content) - -def write_object_to_file(path, content): - dir = os.path.dirname(path) - if not os.path.exists(dir): - os.mkdir(dir) - write_to_file(path, content) + + if mode: + os.chmod(path, mode) def read_file(file_name): with open(file_name, 'r') as f: From 83e97060eb2c747a57262fbd10ad463d03922f56 Mon Sep 17 00:00:00 2001 From: lizherui Date: Wed, 25 Jun 2014 20:50:51 +0800 Subject: [PATCH 18/29] add reset command --- src/repository.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/repository.py b/src/repository.py index c201c1d..18aca65 100644 --- a/src/repository.py +++ b/src/repository.py @@ -145,6 +145,9 @@ def show_status(self): for file in files: print_str += colored('\t%s:\t%s\n' % (change, file), 'green') print_str += '\n' + else: + print_str += '\nno changes added to commit\n\n' + if unstaged_files['modified'] or unstaged_files['deleted']: print_str += 'Changes not staged for commit:\n (use "git add ..." to update what will be committed)\n' @@ -153,6 +156,7 @@ def show_status(self): for file in files: print_str += colored('\t%s:\t%s\n' % (change, file), 'red') print_str += '\n' + if untracked_files: print_str += 'Untracked files:\n (use "git add ..." to include in what will be committed)\n\n' From 49255b2457ba8ec07e1a7eec6f05e72c16f9951c Mon Sep 17 00:00:00 2001 From: lizherui Date: Wed, 25 Jun 2014 21:31:11 +0800 Subject: [PATCH 19/29] add checkout command --- Schedule.md | 7 ++++--- src/branch.py | 9 ++++++++- src/command.py | 23 +++++++++++++++++------ src/git.py | 16 ++++++++++++++++ src/repository.py | 5 ++++- 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/Schedule.md b/Schedule.md index b553d2c..942b90a 100644 --- a/Schedule.md +++ b/Schedule.md @@ -7,12 +7,13 @@ * status * branch * reset +* checkout ###Doing -* push +* diff ###TODO -* clone -* diff * merge +* push +* clone * ...... diff --git a/src/branch.py b/src/branch.py index 8cc899e..2e70bbe 100644 --- a/src/branch.py +++ b/src/branch.py @@ -7,7 +7,7 @@ import shutil from constants import HEAD_PATH, REF_HEADS_DIR -from utils import read_file +from utils import read_file, write_to_file class Branch(object): @@ -40,4 +40,11 @@ def delete_branch(self, name): print "error: Cannot delete the branch '%s' which you are currently on." % (name) exit(1) os.remove(os.path.join(REF_HEADS_DIR, name)) + + def switch_branch(self, name): + if not self._check_branch_exists(name): + print "error: branch '%s' did not match any branches known to git." % (name) + exit(1) + write_to_file(HEAD_PATH, 'ref: refs/heads/%s' % name) + \ No newline at end of file diff --git a/src/command.py b/src/command.py index be26271..5a83384 100644 --- a/src/command.py +++ b/src/command.py @@ -7,6 +7,7 @@ from termcolor import colored +from branch import Branch from constants import GIT_DIR from repository import Repository from utils import get_all_files_in_dir, filter_by_gitignore @@ -49,23 +50,33 @@ def cmd_status(): @staticmethod def cmd_branch(name, is_deleted): - repo = Repository() + b = Branch() if not name: - for branch in repo.branch.get_all_branches(): - print '* %s' % colored(branch, 'green') if branch == repo.branch.head_name else ' %s' % branch + for branch in b.get_all_branches(): + print '* %s' % colored(branch, 'green') if branch == b.head_name else ' %s' % branch elif is_deleted: - repo.branch.delete_branch(name) + b.delete_branch(name) else : - repo.branch.add_branch(name) + b.add_branch(name) @staticmethod def cmd_reset(commit_sha1, is_soft, is_hard): repo = Repository() + pre_entries = dict(repo.index.entries) repo.update_head_commit(commit_sha1) if not is_soft: repo.rebuild_index_from_commit(commit_sha1) if is_hard: - repo.rebuild_working_tree() + repo.rebuild_working_tree(pre_entries) + + @staticmethod + def cmd_checkout(branch): + b = Branch() + b.switch_branch(branch) + repo = Repository() + pre_entries = dict(repo.index.entries) + repo.rebuild_index_from_commit(repo.branch.head_commit) + repo.rebuild_working_tree(pre_entries) @staticmethod def cmd_push(): diff --git a/src/git.py b/src/git.py index 2bde96c..40b8df1 100755 --- a/src/git.py +++ b/src/git.py @@ -174,6 +174,19 @@ def __init__(self, argv): }, ], }, + 'checkout' : { + 'func' : self._checkout, + 'help' : 'Checkout a branch to the working tree', + 'args' : [ + { + 'name' : ['branch'], + 'properties' : + { + 'help' : 'Sha1 of a commit', + } + }, + ], + }, 'push' : { 'func' : self._push, 'help' : 'Update remote refs along with associated objects', @@ -212,6 +225,9 @@ def _branch(self, args): def _reset(self, args): Command.cmd_reset(args.commit_sha1, is_soft=args.soft, is_hard=args.hard) + def _checkout(self, args): + Command.cmd_checkout(args.branch) + def _push(self, args): pass diff --git a/src/repository.py b/src/repository.py index 18aca65..3866a06 100644 --- a/src/repository.py +++ b/src/repository.py @@ -185,7 +185,10 @@ def rebuild_index_from_commit(self, commit_sha1): self.index.write_to_file() self.index = Index(INDEX_PATH) - def rebuild_working_tree(self): + def rebuild_working_tree(self, pre_entries): + for name in set(pre_entries).difference(set(self.index.entries)): + os.remove(name) + for path, properties in self.index.entries.iteritems(): content = Blob(sha1=properties['sha1']).raw_content write_to_file(path, content, mode=properties['mode']) From 4db24d25a2712cc51cde8a4560574d2b86c83bbc Mon Sep 17 00:00:00 2001 From: lizherui Date: Wed, 25 Jun 2014 21:39:07 +0800 Subject: [PATCH 20/29] update command descriptions --- src/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/git.py b/src/git.py index 40b8df1..fc5ca35 100755 --- a/src/git.py +++ b/src/git.py @@ -153,7 +153,7 @@ def __init__(self, argv): 'name' : ['commit_sha1'], 'properties' : { - 'help' : 'Sha1 of a commit', + 'help' : 'Commit to reset', } }, { @@ -182,7 +182,7 @@ def __init__(self, argv): 'name' : ['branch'], 'properties' : { - 'help' : 'Sha1 of a commit', + 'help' : 'Branch to checkout', } }, ], From 054cba0348bebd50e36402ad8ab66e6ca6197511 Mon Sep 17 00:00:00 2001 From: lizherui Date: Thu, 26 Jun 2014 18:33:59 +0800 Subject: [PATCH 21/29] add diff command --- Schedule.md | 7 ++--- src/command.py | 7 +++++ src/git.py | 17 ++++++++++++ src/repository.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++- src/utils.py | 38 ++++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 4 deletions(-) diff --git a/Schedule.md b/Schedule.md index 942b90a..4254968 100644 --- a/Schedule.md +++ b/Schedule.md @@ -8,12 +8,13 @@ * branch * reset * checkout +* diff ###Doing -* diff +* push ###TODO -* merge -* push * clone +* merge +* pull * ...... diff --git a/src/command.py b/src/command.py index 5a83384..0d6adb0 100644 --- a/src/command.py +++ b/src/command.py @@ -78,6 +78,13 @@ def cmd_checkout(branch): repo.rebuild_index_from_commit(repo.branch.head_commit) repo.rebuild_working_tree(pre_entries) + @staticmethod + def cmd_diff(cached): + if cached: + Repository().diff_between_index_and_head_tree() + else: + Repository().diff_between_working_tree_and_index() + @staticmethod def cmd_push(): pass diff --git a/src/git.py b/src/git.py index fc5ca35..587d67d 100755 --- a/src/git.py +++ b/src/git.py @@ -187,6 +187,20 @@ def __init__(self, argv): }, ], }, + 'diff' : { + 'func' : self._diff, + 'help' : 'Show changes between commits, commit and working tree, etc', + 'args' : [ + { + 'name' : ['--cached'], + 'properties' : + { + 'help' : 'Show changes between and the index and the head tree', + 'action' : 'store_true', + } + }, + ], + }, 'push' : { 'func' : self._push, 'help' : 'Update remote refs along with associated objects', @@ -228,6 +242,9 @@ def _reset(self, args): def _checkout(self, args): Command.cmd_checkout(args.branch) + def _diff(self, args): + Command.cmd_diff(args.cached) + def _push(self, args): pass diff --git a/src/repository.py b/src/repository.py index 3866a06..e735e5c 100644 --- a/src/repository.py +++ b/src/repository.py @@ -3,6 +3,7 @@ @author: lzrak47 ''' +from difflib import unified_diff import os import time @@ -15,7 +16,7 @@ from index import Index from objects import Blob, Commit, Tree from utils import get_all_files_in_dir, read_file, write_to_file, cal_mode, \ - less_str, filter_by_gitignore, get_file_mode + less_str, filter_by_gitignore, get_file_mode, diff_file class Repository(object): @@ -192,6 +193,71 @@ def rebuild_working_tree(self, pre_entries): for path, properties in self.index.entries.iteritems(): content = Blob(sha1=properties['sha1']).raw_content write_to_file(path, content, mode=properties['mode']) + + + def diff_between_working_tree_and_index(self): + res = '' + for path in self._get_unstaged_files()['modified']: + old_file = { + 'path' : path, + 'sha1' : self.index.entries[path]['sha1'], + 'mode' : self.index.entries[path]['mode'], + 'content' : Blob(sha1=self.index.entries[path]['sha1']).raw_content, + } + + f = read_file(path) + blob = Blob(f) + new_file = { + 'path' : path, + 'sha1' : blob.sha1, + 'mode' : get_file_mode(path), + 'content' : f, + } + res += diff_file(old_file, new_file) + less_str(res) + + def diff_between_index_and_head_tree(self): + tree = Tree(sha1=Commit(sha1=self.branch.head_commit).tree) + tree_objects = tree.parse_objects() + res = '' + for path in self._get_uncommitted_files()['modified']: + old_file = { + 'path' : path, + 'sha1' : tree_objects[path]['sha1'], + 'mode' : tree_objects[path]['mode'], + 'content' : Blob(sha1=tree_objects[path]['sha1']).raw_content, + } + + new_file = { + 'path' : path, + 'sha1' : self.index.entries[path]['sha1'], + 'mode' : self.index.entries[path]['mode'], + 'content' : Blob(sha1=self.index.entries[path]['sha1']).raw_content, + } + res += diff_file(old_file, new_file) + + for path in self._get_uncommitted_files()['deleted']: + old_file = { + 'path' : path, + 'sha1' : tree_objects[path]['sha1'], + 'mode' : tree_objects[path]['mode'], + 'content' : Blob(sha1=tree_objects[path]['sha1']).raw_content, + } + new_file = {'path':None, 'sha1':'0' * 7, 'mode': None, 'content':'',} + res += diff_file(old_file, new_file) + + for path in self._get_uncommitted_files()['new file']: + old_file = {'path':None, 'sha1':'0' * 7, 'mode': None, 'content':'',} + new_file = { + 'path' : path, + 'sha1' : self.index.entries[path]['sha1'], + 'mode' : self.index.entries[path]['mode'], + 'content' : Blob(sha1=self.index.entries[path]['sha1']).raw_content, + } + res += diff_file(old_file, new_file) + less_str(res) + + diff --git a/src/utils.py b/src/utils.py index 6dce514..409f705 100644 --- a/src/utils.py +++ b/src/utils.py @@ -4,12 +4,14 @@ @author: lzrak47 ''' +from difflib import unified_diff import hashlib import os import stat import tempfile import pathspec +from termcolor import colored from constants import GITIGNORE_PATH @@ -74,6 +76,42 @@ def filter_by_gitignore(raw_list): with open(GITIGNORE_PATH, 'r') as fh: spec = pathspec.PathSpec.from_lines(pathspec.GitIgnorePattern, fh) return set(raw_list).difference(spec.match_files(raw_list)) + +def diff_file(old_file, new_file): + if old_file['path']: + print_str = 'diff --git a/%s b/%s\n' % (old_file['path'], old_file['path']) + else: + print_str = 'diff --git a/%s b/%s\n' % (new_file['path'], new_file['path']) + + if old_file['mode'] == new_file['mode']: + print_str += 'index %.7s..%.7s %04o\n' % (old_file['sha1'], new_file['sha1'], new_file['mode']) + + elif not old_file['mode']: + print_str += 'new file mode %04o\n' % (new_file['mode']) + print_str += 'index %.7s..%.7s\n' % (old_file['sha1'], new_file['sha1']) + + elif not new_file['mode']: + print_str += 'deleted file mode %04o\n' % (old_file['mode']) + print_str += 'index %.7s..%.7s\n' % (old_file['sha1'], new_file['sha1']) + + else: + print_str += 'old mode %04o\n' % (old_file['mode']) + print_str += 'new mode %04o\n' % (new_file['mode']) + print_str += 'index %.7s..%.7s\n' % (old_file['sha1'], new_file['sha1']) + + from_file = 'a/%s' % old_file['path'] if old_file['path'] else '/dev/null' + to_file = 'b/%s' % new_file['path'] if new_file['path'] else '/dev/null' + for i, line in enumerate(unified_diff(old_file['content'].splitlines(), new_file['content'].splitlines(), fromfile=from_file , tofile=to_file)): + str = '%s\n' % line.strip('\n') + if line.startswith('@@'): + print_str += colored(str, 'cyan') + elif line.startswith('+') and i >= 2: + print_str += colored(str, 'green') + elif line.startswith('-') and i >= 2: + print_str += colored(str, 'red') + else: + print_str += str + return print_str class Sha1Reader(object): From 2af266435135a09cbb84a6808ff6ee7d428ec935 Mon Sep 17 00:00:00 2001 From: lizherui Date: Thu, 26 Jun 2014 18:51:32 +0800 Subject: [PATCH 22/29] deleted files diff --- src/repository.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/repository.py b/src/repository.py index e735e5c..e9f6957 100644 --- a/src/repository.py +++ b/src/repository.py @@ -214,6 +214,15 @@ def diff_between_working_tree_and_index(self): 'content' : f, } res += diff_file(old_file, new_file) + for path in self._get_unstaged_files()['deleted']: + old_file = { + 'path' : path, + 'sha1' : self.index.entries[path]['sha1'], + 'mode' : self.index.entries[path]['mode'], + 'content' : Blob(sha1=self.index.entries[path]['sha1']).raw_content, + } + new_file = {'path':None, 'sha1':'0' * 7, 'mode': None, 'content':'',} + res += diff_file(old_file, new_file) less_str(res) def diff_between_index_and_head_tree(self): From 9b83315bde9aa03fdafa3c50e4b69bb2eb604287 Mon Sep 17 00:00:00 2001 From: lizherui Date: Thu, 26 Jun 2014 19:10:32 +0800 Subject: [PATCH 23/29] clean code --- src/repository.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/repository.py b/src/repository.py index e9f6957..01421d8 100644 --- a/src/repository.py +++ b/src/repository.py @@ -3,7 +3,6 @@ @author: lzrak47 ''' -from difflib import unified_diff import os import time From a58d683536901f037ac25ff25e034a8ca3120faa Mon Sep 17 00:00:00 2001 From: jasee Date: Wed, 2 Jul 2014 10:26:09 +0800 Subject: [PATCH 24/29] fix status bug: 'git status' raise 'KeyError' in an empty repository. --- src/repository.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/repository.py b/src/repository.py index 01421d8..4b18f46 100644 --- a/src/repository.py +++ b/src/repository.py @@ -121,7 +121,11 @@ def _get_unstaged_files(self): def _get_uncommitted_files(self): if not self.branch.head_commit: - return {'new file' : self.index.entries} + return { + 'modified' : None, + 'deleted' : None, + 'new file' : self.index.entries, + } tree = Tree(sha1=Commit(sha1=self.branch.head_commit).tree) tree_objects = tree.parse_objects() From 291705b6dacf5ed1a72ff48f3166d6bf7e4e03f5 Mon Sep 17 00:00:00 2001 From: lizherui Date: Fri, 4 Jul 2014 20:14:02 +0800 Subject: [PATCH 25/29] add unit tests --- .gitignore | 2 - CodingStyle.md | 4 +- README.md | 17 +++++-- src/command.py | 30 +++++++----- src/git.py | 15 +----- src/repository.py | 54 +++++++++----------- {unittest => src/test}/__init__.py | 0 src/test/run_test.sh | 2 + src/test/test_add.py | 59 ++++++++++++++++++++++ src/test/test_branch.py | 46 +++++++++++++++++ src/test/test_checkout.py | 56 +++++++++++++++++++++ src/test/test_commit.py | 64 ++++++++++++++++++++++++ src/test/test_diff.py | 75 ++++++++++++++++++++++++++++ src/test/test_init.py | 40 +++++++++++++++ src/test/test_log.py | 48 ++++++++++++++++++ src/test/test_reset.py | 69 ++++++++++++++++++++++++++ src/test/test_rm.py | 49 ++++++++++++++++++ src/test/test_status.py | 79 ++++++++++++++++++++++++++++++ src/utils.py | 2 +- 19 files changed, 646 insertions(+), 65 deletions(-) rename {unittest => src/test}/__init__.py (100%) create mode 100755 src/test/run_test.sh create mode 100644 src/test/test_add.py create mode 100644 src/test/test_branch.py create mode 100644 src/test/test_checkout.py create mode 100644 src/test/test_commit.py create mode 100644 src/test/test_diff.py create mode 100644 src/test/test_init.py create mode 100644 src/test/test_log.py create mode 100644 src/test/test_reset.py create mode 100644 src/test/test_rm.py create mode 100644 src/test/test_status.py diff --git a/.gitignore b/.gitignore index 33337d1..a54a35c 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,3 @@ coverage.xml # Sphinx documentation docs/_build/ - -test.py diff --git a/CodingStyle.md b/CodingStyle.md index beef28f..cd11e3d 100644 --- a/CodingStyle.md +++ b/CodingStyle.md @@ -2,9 +2,7 @@ For code written in Python 2.7, please follow the [PEP8](http://legacy.python.or For code written in C maybe for high performance, please follow [The Official Git Coding Style](https://github.com/git/git/blob/master/Documentation/CodingGuidelines) by Linus Torvalds. -If you want to write some unit testing, please write them in the 'unittest' dir. - -I don't like unit testing at all personally. I mean, if you really like it, do it, I won't oppose it. +If you want to write some unit testing, please write them in the 'src/test' dir. Everything shoud be written in English, includes comments and documents. diff --git a/README.md b/README.md index 16df005..5594c37 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,22 @@ ##Overview -This is the git-in-python project. +This is the git-in-python project, just for *nix environment. It aims to rewrite Git in Python, perhaps with some C code for high performance. -[git.py](https://github.com/lizherui/git-in-python/blob/master/src/git.py) is the entrance of the whole project. +[git.py](https://github.com/lizherui/git-in-python/blob/master/src/git.py) is the entrance of the whole project, + +Before starting, please read the [CodingStyle](https://github.com/lizherui/git-in-python/blob/master/CodingStyle.md) and the [Schedule](https://github.com/lizherui/git-in-python/blob/master/Schedule.md). + +Before running, make sure having installed all the 3rd-party packages: + + pip install -r requirements.txt + +Before contrubuting, make sure having passed all the unit tests: + + cd src/test + ./run_test.sh + -Please read the [CodingStyle](https://github.com/lizherui/git-in-python/blob/master/CodingStyle.md) and [Schedule](https://github.com/lizherui/git-in-python/blob/master/Schedule.md) before contributing, pip install -r [requirements.txt](https://github.com/lizherui/git-in-python/blob/master/requirements.txt) before running. ##Why [The Official Git](https://github.com/git/git) written in C attracts hackers all over the world since created. diff --git a/src/command.py b/src/command.py index 0d6adb0..4a3e202 100644 --- a/src/command.py +++ b/src/command.py @@ -10,7 +10,7 @@ from branch import Branch from constants import GIT_DIR from repository import Repository -from utils import get_all_files_in_dir, filter_by_gitignore +from utils import get_all_files_in_dir, filter_by_gitignore, less_str class Command(object): @@ -20,8 +20,8 @@ class Command(object): ''' @staticmethod - def cmd_init(workspace, bare): - Repository.create_repository(workspace, bare) + def cmd_init(workspace): + Repository.create_repository(workspace) @staticmethod def cmd_add(file): @@ -31,7 +31,7 @@ def cmd_add(file): Repository().stage([file]) @staticmethod - def cmd_rm(file, cached): + def cmd_rm(file, cached=False): Repository().delete(file) if not cached: os.remove(file) @@ -41,15 +41,19 @@ def cmd_commit(msg): Repository().commit(msg) @staticmethod - def cmd_log(num): - Repository().show_log(num) + def cmd_log(num, use_less=True): + res = Repository().show_log(num) + if use_less: + less_str(res) + else: + print res @staticmethod def cmd_status(): Repository().show_status() @staticmethod - def cmd_branch(name, is_deleted): + def cmd_branch(name, is_deleted=False): b = Branch() if not name: for branch in b.get_all_branches(): @@ -60,7 +64,7 @@ def cmd_branch(name, is_deleted): b.add_branch(name) @staticmethod - def cmd_reset(commit_sha1, is_soft, is_hard): + def cmd_reset(commit_sha1, is_soft=False, is_hard=False): repo = Repository() pre_entries = dict(repo.index.entries) repo.update_head_commit(commit_sha1) @@ -79,11 +83,15 @@ def cmd_checkout(branch): repo.rebuild_working_tree(pre_entries) @staticmethod - def cmd_diff(cached): + def cmd_diff(cached=False, use_less=True): if cached: - Repository().diff_between_index_and_head_tree() + res = Repository().diff_between_index_and_head_tree() + else: + res = Repository().diff_between_working_tree_and_index() + if use_less: + less_str(res) else: - Repository().diff_between_working_tree_and_index() + print res @staticmethod def cmd_push(): diff --git a/src/git.py b/src/git.py index 587d67d..69a85a1 100755 --- a/src/git.py +++ b/src/git.py @@ -7,7 +7,6 @@ ''' import argparse -import os import sys from command import Command @@ -31,18 +30,9 @@ def __init__(self, argv): { 'help' : 'Directory of the git repository', 'nargs' : '?', - 'default' : '', + 'default' : './', }, }, - - { - 'name' : ['--bare'], - 'properties' : - { - 'help' : 'Create a bare repository', - 'action' : 'store_true', - } - } ] }, @@ -215,8 +205,7 @@ def __init__(self, argv): } def _init(self, args): - workspace = os.path.join(os.getcwd(), args.directory) - Command.cmd_init(workspace=workspace, bare=args.bare) + Command.cmd_init(workspace=args.directory) def _add(self, args): Command.cmd_add(args.file) diff --git a/src/repository.py b/src/repository.py index 4b18f46..4f529e4 100644 --- a/src/repository.py +++ b/src/repository.py @@ -45,13 +45,12 @@ def stage(self, files): print 'stage file %s error: %s' % (file, e) @staticmethod - def create_repository(workspace, bare=False): + def create_repository(workspace): if not os.path.exists(workspace): os.mkdir(workspace) os.chdir(workspace) - if not bare: - os.mkdir(GIT_DIR) + os.mkdir(GIT_DIR) for new_dir in INIT_DIR: os.mkdir(new_dir) @@ -66,7 +65,7 @@ def create_repository(workspace, bare=False): 'core': { 'repositoryformatversion' : '0', 'filemode' : 'true', - 'bare' : str(bare).lower(), + 'bare' : 'true', 'logallrefupdates' : 'true', } } @@ -99,13 +98,13 @@ def show_log(self, num): parent_commit = Commit(sha1=cur_commit.parent_sha1) print_str += '\n%s' % (parent_commit.raw_content) cur_commit = parent_commit - less_str(print_str) + return print_str - def _get_untracked_files(self): + def get_untracked_files(self): raw_list = list(set(self.working_tree_files).difference(set(list(self.index.entries)))) return filter_by_gitignore(raw_list) - def _get_unstaged_files(self): + def get_unstaged_files(self): res = { 'modified': [], 'deleted' : [], @@ -119,11 +118,11 @@ def _get_unstaged_files(self): return res - def _get_uncommitted_files(self): + def get_uncommitted_files(self): if not self.branch.head_commit: return { - 'modified' : None, - 'deleted' : None, + 'modified' : [], + 'deleted' : [], 'new file' : self.index.entries, } @@ -133,14 +132,14 @@ def _get_uncommitted_files(self): 'modified': [name for name in set(self.index.entries).intersection(set(tree_objects)) \ if self.index.entries[name]['sha1'] != tree_objects[name]['sha1'] or \ self.index.entries[name]['mode'] != tree_objects[name]['mode']], - 'deleted' : set(tree_objects).difference(self.index.entries), - 'new file' : set(self.index.entries).difference(set(tree_objects)), + 'deleted' : list(set(tree_objects).difference(self.index.entries)), + 'new file' : list(set(self.index.entries).difference(set(tree_objects))), } def show_status(self): - untracked_files = self._get_untracked_files() - unstaged_files = self._get_unstaged_files() - uncommitted_files = self._get_uncommitted_files() + untracked_files = self.get_untracked_files() + unstaged_files = self.get_unstaged_files() + uncommitted_files = self.get_uncommitted_files() print_str = 'On branch %s\n' % (self.branch.head_name) if uncommitted_files['modified'] or uncommitted_files['deleted'] or uncommitted_files['new file']: @@ -200,7 +199,7 @@ def rebuild_working_tree(self, pre_entries): def diff_between_working_tree_and_index(self): res = '' - for path in self._get_unstaged_files()['modified']: + for path in self.get_unstaged_files()['modified']: old_file = { 'path' : path, 'sha1' : self.index.entries[path]['sha1'], @@ -217,7 +216,7 @@ def diff_between_working_tree_and_index(self): 'content' : f, } res += diff_file(old_file, new_file) - for path in self._get_unstaged_files()['deleted']: + for path in self.get_unstaged_files()['deleted']: old_file = { 'path' : path, 'sha1' : self.index.entries[path]['sha1'], @@ -226,13 +225,13 @@ def diff_between_working_tree_and_index(self): } new_file = {'path':None, 'sha1':'0' * 7, 'mode': None, 'content':'',} res += diff_file(old_file, new_file) - less_str(res) + return res def diff_between_index_and_head_tree(self): tree = Tree(sha1=Commit(sha1=self.branch.head_commit).tree) tree_objects = tree.parse_objects() res = '' - for path in self._get_uncommitted_files()['modified']: + for path in self.get_uncommitted_files()['modified']: old_file = { 'path' : path, 'sha1' : tree_objects[path]['sha1'], @@ -248,7 +247,7 @@ def diff_between_index_and_head_tree(self): } res += diff_file(old_file, new_file) - for path in self._get_uncommitted_files()['deleted']: + for path in self.get_uncommitted_files()['deleted']: old_file = { 'path' : path, 'sha1' : tree_objects[path]['sha1'], @@ -258,7 +257,7 @@ def diff_between_index_and_head_tree(self): new_file = {'path':None, 'sha1':'0' * 7, 'mode': None, 'content':'',} res += diff_file(old_file, new_file) - for path in self._get_uncommitted_files()['new file']: + for path in self.get_uncommitted_files()['new file']: old_file = {'path':None, 'sha1':'0' * 7, 'mode': None, 'content':'',} new_file = { 'path' : path, @@ -267,14 +266,5 @@ def diff_between_index_and_head_tree(self): 'content' : Blob(sha1=self.index.entries[path]['sha1']).raw_content, } res += diff_file(old_file, new_file) - less_str(res) - - - - - - - - - - + return res + \ No newline at end of file diff --git a/unittest/__init__.py b/src/test/__init__.py similarity index 100% rename from unittest/__init__.py rename to src/test/__init__.py diff --git a/src/test/run_test.sh b/src/test/run_test.sh new file mode 100755 index 0000000..f47f61b --- /dev/null +++ b/src/test/run_test.sh @@ -0,0 +1,2 @@ +export PYTHONPATH=..:$PYTHONPATH +python -m unittest discover -v diff --git a/src/test/test_add.py b/src/test/test_add.py new file mode 100644 index 0000000..88ec7fe --- /dev/null +++ b/src/test/test_add.py @@ -0,0 +1,59 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from command import Command +from objects import Blob +from repository import Repository +from utils import write_to_file, get_file_mode + + +class TestAdd(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_add' + Command.cmd_init(self.workspace) + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + def _check_blob_and_index(self, *paths_contents): + entries = Repository().index.entries + for path, content in paths_contents: + sha1 = Blob(content).sha1 + blob = Blob(sha1=sha1) + self.assertEqual(blob.raw_content, content) + self.assertEqual(entries[path]['sha1'], sha1) + self.assertEqual(entries[path]['mode'], get_file_mode(path)) + + def test_add_file(self): + path = '1.txt' + content = '1\n' + write_to_file(path, content) + Command.cmd_add(path) + self._check_blob_and_index((path, content)) + + def test_add_dir(self): + path = os.path.join('dir', '2.txt') + content = '2\n' + write_to_file(path, content) + Command.cmd_add(path) + self._check_blob_and_index((path, content)) + + def test_add_all(self): + paths_contents = [('1.txt', '1\n'), (os.path.join('dir', '2.txt'), '2\n')] + for path, content in paths_contents: + write_to_file(path, content) + Command.cmd_add('.') + self._check_blob_and_index(*paths_contents) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_branch.py b/src/test/test_branch.py new file mode 100644 index 0000000..50db002 --- /dev/null +++ b/src/test/test_branch.py @@ -0,0 +1,46 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from branch import Branch +from command import Command + + +class TestBranch(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_branch' + Command.cmd_init(self.workspace) + Command.cmd_commit('first ci') + self.new_branch = 'new_branch' + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + + def test_branch_add(self): + Command.cmd_branch(self.new_branch) + self.assertIn(self.new_branch, Branch().get_all_branches()) + + def test_branch_delete(self): + Command.cmd_branch(self.new_branch) + self.assertIn(self.new_branch, Branch().get_all_branches()) + Command.cmd_branch(self.new_branch, True) + self.assertNotIn(self.new_branch, Branch().get_all_branches()) + + def test_branch_list(self): + Command.cmd_branch('') + Command.cmd_branch(self.new_branch) + Command.cmd_branch('') + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_checkout.py b/src/test/test_checkout.py new file mode 100644 index 0000000..4d4bb74 --- /dev/null +++ b/src/test/test_checkout.py @@ -0,0 +1,56 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from branch import Branch +from command import Command +from utils import write_to_file, read_file + + +class TestCheckout(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_branch' + Command.cmd_init(self.workspace) + Command.cmd_commit('first ci') + self.file_list = [('1.txt', '1\n'), ('2.txt', '2\n')] + for path, content in self.file_list: + write_to_file(path, content) + Command.cmd_add(path) + Command.cmd_commit('master ci') + + self.new_branch = 'new_branch' + Command.cmd_branch(self.new_branch) + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + + def test_checkout(self): + Command.cmd_checkout(self.new_branch) + self.assertEqual(Branch().head_name, self.new_branch) + + write_to_file(self.file_list[0][0], '11\n') + Command.cmd_rm(self.file_list[1][0]) + new_path = '3.txt' + new_content = '3\n' + write_to_file(new_path, new_content) + Command.cmd_add('.') + Command.cmd_commit('branch ci') + + Command.cmd_checkout('master') + self.assertTrue(os.path.exists(self.file_list[1][0])) + self.assertFalse(os.path.exists(new_path)) + self.assertEqual(read_file(self.file_list[0][0]), self.file_list[0][1]) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_commit.py b/src/test/test_commit.py new file mode 100644 index 0000000..4cba785 --- /dev/null +++ b/src/test/test_commit.py @@ -0,0 +1,64 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from branch import Branch +from command import Command +from objects import Commit, Tree, Blob +from utils import write_to_file + + +class TestCommit(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_commit' + Command.cmd_init(self.workspace) + self.path = '1.txt' + self.content = '1\n' + write_to_file(self.path, self.content) + Command.cmd_add(self.path) + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + + def test_commit_once(self): + Command.cmd_commit('first ci') + commit = Commit(sha1=Branch().head_commit) + self.assertIsNone(commit.parent_sha1) + tree = Tree(sha1=commit.tree) + objects = tree.parse_objects() + self.assertEqual(objects[self.path]['sha1'], Blob(self.content).sha1) + + def test_commit_twice(self): + Command.cmd_commit('first ci') + parent_sha1 = Branch().head_commit + + second_content = '11\n' + write_to_file(self.path, second_content) + + new_path = '2.txt' + new_content = '2\n' + write_to_file(new_path, new_content) + + Command.cmd_add('.') + Command.cmd_commit('second ci') + + commit = Commit(sha1=Branch().head_commit) + self.assertEqual(parent_sha1, commit.parent_sha1) + tree = Tree(sha1=commit.tree) + objects = tree.parse_objects() + self.assertEqual(objects[self.path]['sha1'], Blob(second_content).sha1) + self.assertEqual(objects[new_path]['sha1'], Blob(new_content).sha1) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_diff.py b/src/test/test_diff.py new file mode 100644 index 0000000..e12ff4c --- /dev/null +++ b/src/test/test_diff.py @@ -0,0 +1,75 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from command import Command +from utils import write_to_file + + +class TestDiff(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_diff' + Command.cmd_init(self.workspace) + self.old_content = ''' + The Way that can be told of is not the eternal Way; + The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; + The Named is the mother of all things. + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, + so we may see their outcome. + The two are the same, + But after they are produced, + they have different names. + ''' + self.new_content = ''' + The Nameless is the origin of Heaven and Earth; + The named is the mother of all things. + + Therefore let there always be non-being, + so we may see their subtlety, + And let there always be being, + so we may see their outcome. + The two are the same, + But after they are produced, + they have different names. + They both may be called deep and profound. + Deeper and more profound, + The door of all subtleties! + ''' + self.file_list = [('1.txt', self.old_content), ('2.txt', self.old_content)] + for path, content in self.file_list: + write_to_file(path, content) + Command.cmd_add(path) + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + + def test_diff_default(self): + write_to_file(self.file_list[0][0], self.new_content) + os.remove(self.file_list[1][0]) + Command.cmd_diff(False, False) + + def test_diff_cached(self): + Command.cmd_commit('first ci') + write_to_file(self.file_list[0][0], self.new_content) + Command.cmd_rm(self.file_list[1][0]) + new_path = '3.txt' + write_to_file(new_path, self.new_content) + Command.cmd_add('.') + Command.cmd_diff(True, False) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_init.py b/src/test/test_init.py new file mode 100644 index 0000000..114d46a --- /dev/null +++ b/src/test/test_init.py @@ -0,0 +1,40 @@ +''' +Created on Jul 2, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from command import Command +from constants import GIT_DIR, INIT_DIR, INIT_FILE +from utils import read_file + + +class TestInit(unittest.TestCase): + + def _check_dirs_and_files(self, workspace): + Command.cmd_init(workspace) + self.assertTrue(os.path.exists(GIT_DIR)) + for dir in INIT_DIR: + self.assertTrue(os.path.exists(dir)) + for file in INIT_FILE: + path = file[0] + content = file[1] + self.assertEqual(read_file(path), content) + + def test_init_with_workspace(self): + workspace = 'test_init' + self._check_dirs_and_files(workspace) + os.chdir('..') + shutil.rmtree(workspace) + + def test_init_without_workspace(self): + workspace = './' + self._check_dirs_and_files(workspace) + shutil.rmtree(GIT_DIR) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test/test_log.py b/src/test/test_log.py new file mode 100644 index 0000000..c333ff8 --- /dev/null +++ b/src/test/test_log.py @@ -0,0 +1,48 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from command import Command +from utils import write_to_file + + +class TestLog(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_log' + Command.cmd_init(self.workspace) + + self.path = '1.txt' + self.content = '1\n' + write_to_file(self.path, self.content) + + Command.cmd_add(self.path) + Command.cmd_commit('first ci') + + second_content = '11\n' + write_to_file(self.path, second_content) + + Command.cmd_add(self.path) + Command.cmd_commit('second ci') + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + + def test_log_without_num(self): + Command.cmd_log(float('infinity'), False) + + def test_log_with_num(self): + Command.cmd_log(1, False) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_reset.py b/src/test/test_reset.py new file mode 100644 index 0000000..c796db7 --- /dev/null +++ b/src/test/test_reset.py @@ -0,0 +1,69 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from branch import Branch +from command import Command +from repository import Repository +from utils import write_to_file, read_file + + +class Test(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_reset' + Command.cmd_init(self.workspace) + + self.path, self.content = ('1.txt', '1\n') + write_to_file(self.path, self.content) + Command.cmd_add(self.path) + Command.cmd_commit('first ci') + self.first_commit = Branch().head_commit + + write_to_file(self.path, '2.txt') + Command.cmd_add(self.path) + Command.cmd_commit('second ci') + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + + def test_reset_soft(self): + Command.cmd_reset(self.first_commit, is_soft=True, is_hard=False) + self.assertEqual(Branch().head_commit, self.first_commit) + repo = Repository() + uncommitted_files = repo.get_uncommitted_files() + unstaged_files = repo.get_unstaged_files() + self.assertIn(self.path, uncommitted_files['modified']) + self.assertFalse(unstaged_files['modified']) + + def test_reset_default(self): + Command.cmd_reset(self.first_commit, is_soft=False, is_hard=False) + self.assertEqual(Branch().head_commit, self.first_commit) + repo = Repository() + uncommitted_files = repo.get_uncommitted_files() + unstaged_files = repo.get_unstaged_files() + self.assertFalse(uncommitted_files['modified']) + self.assertIn(self.path, unstaged_files['modified']) + + def test_reset_hard(self): + Command.cmd_reset(self.first_commit, is_soft=False, is_hard=True) + self.assertEqual(Branch().head_commit, self.first_commit) + repo = Repository() + uncommitted_files = repo.get_uncommitted_files() + unstaged_files = repo.get_unstaged_files() + self.assertFalse(uncommitted_files['modified']) + self.assertFalse(unstaged_files['modified']) + self.assertEqual(read_file(self.path), self.content) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_rm.py b/src/test/test_rm.py new file mode 100644 index 0000000..441e2cc --- /dev/null +++ b/src/test/test_rm.py @@ -0,0 +1,49 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from command import Command +from repository import Repository +from utils import write_to_file + + +class TestRm(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_rm' + Command.cmd_init(self.workspace) + self.path = '1.txt' + content = '1\n' + write_to_file(self.path, content) + Command.cmd_add(self.path) + + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + + def test_rm_cached(self): + entries = Repository().index.entries + self.assertIn(self.path, entries) + Command.cmd_rm(self.path, True) + entries = Repository().index.entries + self.assertNotIn(self.path, entries) + + def test_rm_no_cached(self): + entries = Repository().index.entries + self.assertIn(self.path, entries) + Command.cmd_rm(self.path, False) + entries = Repository().index.entries + self.assertNotIn(self.path, entries) + self.assertFalse(os.path.exists(self.path)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/test/test_status.py b/src/test/test_status.py new file mode 100644 index 0000000..7f3d968 --- /dev/null +++ b/src/test/test_status.py @@ -0,0 +1,79 @@ +''' +Created on Jul 4, 2014 + +@author: lzrak47 +''' +import os +import shutil +import unittest + +from command import Command +from repository import Repository +from utils import write_to_file + + +class TestStatus(unittest.TestCase): + + + def setUp(self): + self.workspace = 'test_status' + Command.cmd_init(self.workspace) + + def tearDown(self): + os.chdir('..') + shutil.rmtree(self.workspace) + + def test_status_init(self): + repo = Repository() + untracked_files = repo.get_untracked_files() + self.assertFalse(untracked_files) + Command.cmd_status() + + def test_status_untracked_files(self): + path, content = ('1.txt', '1\n') + write_to_file(path, content) + repo = Repository() + untracked_files = repo.get_untracked_files() + self.assertEqual(untracked_files, ['1.txt']) + Command.cmd_status() + + def test_status_unstaged_files(self): + file_list = [('1.txt', '1\n'), ('2.txt', '2\n')] + for path, content in file_list: + write_to_file(path, content) + Command.cmd_add(path) + + write_to_file(file_list[0][0], '11\n') + os.remove(file_list[1][0]) + + repo = Repository() + unstaged_files = repo.get_unstaged_files() + + self.assertEqual(unstaged_files['modified'], [file_list[0][0]]) + self.assertEqual(unstaged_files['deleted'], [file_list[1][0]]) + Command.cmd_status() + + def test_status_uncommitted_files(self): + file_list = [('1.txt', '1\n'), ('2.txt', '2\n')] + for path, content in file_list: + write_to_file(path, content) + Command.cmd_add(path) + Command.cmd_commit('first ci') + + write_to_file(file_list[0][0], '11\n') + Command.cmd_rm(file_list[1][0]) + new_path = '3.txt' + new_content = '3\n' + write_to_file(new_path, new_content) + Command.cmd_add('.') + + repo = Repository() + uncommitted_files = repo.get_uncommitted_files() + self.assertEqual(uncommitted_files['modified'], [file_list[0][0]]) + self.assertEqual(uncommitted_files['deleted'], [file_list[1][0]]) + self.assertEqual(uncommitted_files['new file'], [new_path]) + Command.cmd_status() + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index 409f705..cb540c8 100644 --- a/src/utils.py +++ b/src/utils.py @@ -75,7 +75,7 @@ def filter_by_gitignore(raw_list): else: with open(GITIGNORE_PATH, 'r') as fh: spec = pathspec.PathSpec.from_lines(pathspec.GitIgnorePattern, fh) - return set(raw_list).difference(spec.match_files(raw_list)) + return list(set(raw_list).difference(spec.match_files(raw_list))) def diff_file(old_file, new_file): if old_file['path']: From 879dabd34d5302e59b872ddca81f46385987d19a Mon Sep 17 00:00:00 2001 From: lizherui Date: Fri, 4 Jul 2014 20:21:00 +0800 Subject: [PATCH 26/29] modify some word --- CodingStyle.md | 2 +- README.md | 11 ++++++----- src/{test => tests}/__init__.py | 0 src/{test/run_test.sh => tests/run_all_tests.sh} | 0 src/{test => tests}/test_add.py | 0 src/{test => tests}/test_branch.py | 0 src/{test => tests}/test_checkout.py | 0 src/{test => tests}/test_commit.py | 0 src/{test => tests}/test_diff.py | 0 src/{test => tests}/test_init.py | 0 src/{test => tests}/test_log.py | 0 src/{test => tests}/test_reset.py | 0 src/{test => tests}/test_rm.py | 0 src/{test => tests}/test_status.py | 0 14 files changed, 7 insertions(+), 6 deletions(-) rename src/{test => tests}/__init__.py (100%) rename src/{test/run_test.sh => tests/run_all_tests.sh} (100%) rename src/{test => tests}/test_add.py (100%) rename src/{test => tests}/test_branch.py (100%) rename src/{test => tests}/test_checkout.py (100%) rename src/{test => tests}/test_commit.py (100%) rename src/{test => tests}/test_diff.py (100%) rename src/{test => tests}/test_init.py (100%) rename src/{test => tests}/test_log.py (100%) rename src/{test => tests}/test_reset.py (100%) rename src/{test => tests}/test_rm.py (100%) rename src/{test => tests}/test_status.py (100%) diff --git a/CodingStyle.md b/CodingStyle.md index cd11e3d..fbb1f54 100644 --- a/CodingStyle.md +++ b/CodingStyle.md @@ -2,7 +2,7 @@ For code written in Python 2.7, please follow the [PEP8](http://legacy.python.or For code written in C maybe for high performance, please follow [The Official Git Coding Style](https://github.com/git/git/blob/master/Documentation/CodingGuidelines) by Linus Torvalds. -If you want to write some unit testing, please write them in the 'src/test' dir. +If you want to write some unit tests, please write them in the 'src/tests' dir. Everything shoud be written in English, includes comments and documents. diff --git a/README.md b/README.md index 5594c37..296d69d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ ##Overview -This is the git-in-python project, just for *nix environment. +This is the git-in-python project, for **nix* environment. It aims to rewrite Git in Python, perhaps with some C code for high performance. -[git.py](https://github.com/lizherui/git-in-python/blob/master/src/git.py) is the entrance of the whole project, + +The [git.py](https://github.com/lizherui/git-in-python/blob/master/src/git.py) is the entrance of the whole project. Before starting, please read the [CodingStyle](https://github.com/lizherui/git-in-python/blob/master/CodingStyle.md) and the [Schedule](https://github.com/lizherui/git-in-python/blob/master/Schedule.md). @@ -11,10 +12,10 @@ Before running, make sure having installed all the 3rd-party packages: pip install -r requirements.txt -Before contrubuting, make sure having passed all the unit tests: +Before contributing, make sure having passed all the unit tests: - cd src/test - ./run_test.sh + cd src/tests + ./run_all_tests.sh diff --git a/src/test/__init__.py b/src/tests/__init__.py similarity index 100% rename from src/test/__init__.py rename to src/tests/__init__.py diff --git a/src/test/run_test.sh b/src/tests/run_all_tests.sh similarity index 100% rename from src/test/run_test.sh rename to src/tests/run_all_tests.sh diff --git a/src/test/test_add.py b/src/tests/test_add.py similarity index 100% rename from src/test/test_add.py rename to src/tests/test_add.py diff --git a/src/test/test_branch.py b/src/tests/test_branch.py similarity index 100% rename from src/test/test_branch.py rename to src/tests/test_branch.py diff --git a/src/test/test_checkout.py b/src/tests/test_checkout.py similarity index 100% rename from src/test/test_checkout.py rename to src/tests/test_checkout.py diff --git a/src/test/test_commit.py b/src/tests/test_commit.py similarity index 100% rename from src/test/test_commit.py rename to src/tests/test_commit.py diff --git a/src/test/test_diff.py b/src/tests/test_diff.py similarity index 100% rename from src/test/test_diff.py rename to src/tests/test_diff.py diff --git a/src/test/test_init.py b/src/tests/test_init.py similarity index 100% rename from src/test/test_init.py rename to src/tests/test_init.py diff --git a/src/test/test_log.py b/src/tests/test_log.py similarity index 100% rename from src/test/test_log.py rename to src/tests/test_log.py diff --git a/src/test/test_reset.py b/src/tests/test_reset.py similarity index 100% rename from src/test/test_reset.py rename to src/tests/test_reset.py diff --git a/src/test/test_rm.py b/src/tests/test_rm.py similarity index 100% rename from src/test/test_rm.py rename to src/tests/test_rm.py diff --git a/src/test/test_status.py b/src/tests/test_status.py similarity index 100% rename from src/test/test_status.py rename to src/tests/test_status.py From 885143414f0bf38f97df5db8fb1b5e4bbe4ccdfa Mon Sep 17 00:00:00 2001 From: lizherui Date: Fri, 4 Jul 2014 20:24:33 +0800 Subject: [PATCH 27/29] add author --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 2e599b7..65700c5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ lzrak47 +jasee From 5375649000db6d71262e70925819529599681328 Mon Sep 17 00:00:00 2001 From: lizherui Date: Fri, 4 Jul 2014 20:25:23 +0800 Subject: [PATCH 28/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 296d69d..eccb2d5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ##Overview -This is the git-in-python project, for **nix* environment. +This is the git-in-python project, for *nix environment. It aims to rewrite Git in Python, perhaps with some C code for high performance. From 7d8d6da9f3f9d1caf8fec441156f185c68a0ecbe Mon Sep 17 00:00:00 2001 From: Nurmukhametov Alexey Date: Thu, 24 Jul 2014 00:29:29 +0400 Subject: [PATCH 29/29] Fixed issue#3 --- src/repository.py | 15 ++++++++------- src/tests/test_init.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/repository.py b/src/repository.py index 4f529e4..58d83ee 100644 --- a/src/repository.py +++ b/src/repository.py @@ -50,15 +50,16 @@ def create_repository(workspace): os.mkdir(workspace) os.chdir(workspace) - os.mkdir(GIT_DIR) + if not os.path.exists(GIT_DIR): + os.mkdir(GIT_DIR) - for new_dir in INIT_DIR: - os.mkdir(new_dir) + for new_dir in INIT_DIR: + os.mkdir(new_dir) - for file_and_content in INIT_FILE: - file_name = file_and_content[0] - content = file_and_content[1] - write_to_file(file_name, content) + for file_and_content in INIT_FILE: + file_name = file_and_content[0] + content = file_and_content[1] + write_to_file(file_name, content) init_config_dict = { diff --git a/src/tests/test_init.py b/src/tests/test_init.py index 114d46a..baa9841 100644 --- a/src/tests/test_init.py +++ b/src/tests/test_init.py @@ -35,6 +35,18 @@ def test_init_without_workspace(self): self._check_dirs_and_files(workspace) shutil.rmtree(GIT_DIR) + def test_init_in_existing_repository(self): + workspace = "test" + Command.cmd_init(workspace) + os.chdir('..') + try: + Command.cmd_init(workspace) + except OSError: + self.assertEqual(1, 2, "Reinitialized existing repository failed") + finally: + os.chdir('..') + shutil.rmtree(workspace) + if __name__ == "__main__": unittest.main()