git.py 5.66 KB
Newer Older
1
2
3
import re
import time

Samuel Sirois's avatar
Samuel Sirois committed
4
5
from os import path

6
from fabric.api import task, env, execute
Samuel Sirois's avatar
Samuel Sirois committed
7
8
from fabric.api import task, env, local
from fabric.colors import red, green, yellow
9
from fabric.contrib.console import confirm
Samuel Sirois's avatar
Samuel Sirois committed
10
11

import helpers as h
12
13
14

from .environments import e

Samuel Sirois's avatar
Samuel Sirois committed
15
16
17
18
19
20

@task(alias='is_dirty')
def check_status():
    """
    Check workspace's git repositories status.
    """
21
    if (is_git_dirty()):
22
        print red('Your workspace is not clean.')
Samuel Sirois's avatar
Samuel Sirois committed
23
    else:
24
        print green('Your workspace is clean.')
25

26

27
def is_git_dirty():
28
    repos = local('find ' + path.normpath(env.workspace) + ' -name ".git"', capture=True).splitlines()
29
30
    repos = _remove_untouched_repository(repos)

Samuel Sirois's avatar
Samuel Sirois committed
31
32
    nbWarnings = 0
    for repo in repos:
33
        repoLocalPath = path.normpath(path.join(repo, '..'))
34
        nbWarnings += _check_repo(repoLocalPath)
Samuel Sirois's avatar
Samuel Sirois committed
35

36
    return (nbWarnings > 0)
Samuel Sirois's avatar
Samuel Sirois committed
37
38
39
40
41
42
43


    # STEP 2
    # plutot que s'arreter ou continuer betement, on est intelligent et on demande quoi faire:
    # - s'il y a du code non-stage, en faire un commit
    # - si la Branch n'est pas trackee, la pusher
    # - s'il y a des commits non pushes, les pusher
44
    # - attention aux conflits, faire un pull d'abord et valider la fusion automatique
Samuel Sirois's avatar
Samuel Sirois committed
45

46

47
48
49
50
51
52
53
def _remove_untouched_repository(repos):
    repos.remove(path.normpath(path.join(env.workspace, '.git')))
    repos.remove(path.normpath(path.join(env.workspace, 'fabfile', '.git')))

    return repos


54
def _check_repo(repoLocalPath):
55
56
57
58
59
    nbWarnings = 0
    with h.fab_cd('local', repoLocalPath):
        print green('---')
        print green('Verify repo in ' + repoLocalPath)

60
        remoteName = local('LC_ALL=C git remote', capture=True)
61

62
        filesStatusRawInfo = _get_files_status_information()
63
        print green('Verify local files status against current HEAD commit...')
64
        nbWarnings += _check_files_status_vs_head_commit(filesStatusRawInfo)
Philippe Mouchel's avatar
Philippe Mouchel committed
65

66
        localBranchesRawInfo = _get_local_branches_information()
67
        print green('Verify local branches exist on remote "' + remoteName + '"...');
68
        nbWarnings += _check_local_branches_exist_on_remote(localBranchesRawInfo, remoteName)
69
70

        print green('Verify branches status against remote...');
71
        nbWarnings += _check_local_branches_status_vs_remote(localBranchesRawInfo, remoteName)
72
73
74
75

        return nbWarnings


76
def _check_files_status_vs_head_commit(filesStatusRawInfo):
Philippe Mouchel's avatar
Philippe Mouchel committed
77
    nbWarnings = 0
78
79
80
81
82
83
84
    addableFiles = []
    if (len(filesStatusRawInfo) > 0):
        for fileStatus in filesStatusRawInfo:
            fileStatusData = fileStatus.split()
            # Break loop if filename is "fabfile"
            if fileStatusData[1] == 'fabfile':
                break
85

86
87
            nbWarnings += 1
            addableFiles.append(fileStatusData[1])
88

89
90
91
92
93
94
95
96
            print yellow('File "' + fileStatusData[1] + '" ' + {
                'M': 'has un-commited modifications.',
                'D': 'has been deleted.',
                '??': 'is not indexed.',
            }.get(fileStatusData[0], 'is in an unknown state (' + fileStatusData[0] + ')'))

    return nbWarnings

97

98
def _check_local_branches_exist_on_remote(localBranchesRawInfo, remoteName):
99
100
    nbWarnings = 0
    pushableBranches = []
Philippe Mouchel's avatar
Philippe Mouchel committed
101
    for localBranchRawInfo in localBranchesRawInfo:
102
103
        localBranchName = _get_branch_name(localBranchRawInfo)
        if ((localBranchName is not None) and (not _remote_branch_exists(localBranchName))):
Philippe Mouchel's avatar
Philippe Mouchel committed
104
            nbWarnings += 1
105
            pushableBranches.append(localBranchName)
Philippe Mouchel's avatar
Philippe Mouchel committed
106
            print yellow('Local branch "' + localBranchName + '" is not present on "' + remoteName + '" remote.')
107

108
109
110
111
112
    # On suggere de pusher la(les) branche(s) qui sont seulement sur le local
    if (nbWarnings > 0):
        if (confirm(red('There are many local branches not present on remote. Do you want to sync theses?'), default=False)):
            for branchName in pushableBranches:
                local('git push --set-upstream ' + remoteName + ' ' + branchName)
113

114
115
            # Do not alert with diff as local branches are now pushed
            nbWarnings = 0
116

Philippe Mouchel's avatar
Philippe Mouchel committed
117
118
    return nbWarnings

119

120
def _check_local_branches_status_vs_remote(localBranchesRawInfo, remoteName):
Philippe Mouchel's avatar
Philippe Mouchel committed
121
    nbWarnings = 0
122
    pushableBranches = []
123
    pattern = re.compile('.*\[.* (ahead) .*\].*')
Philippe Mouchel's avatar
Philippe Mouchel committed
124
125
    for localBranchRawInfo in localBranchesRawInfo:
        if (pattern.match(localBranchRawInfo)):
126
            localBranchName = _get_branch_name(localBranchRawInfo)
Philippe Mouchel's avatar
Philippe Mouchel committed
127
            nbWarnings += 1
128
129
130
131
132
133
134
135
            pushableBranches.append(localBranchName)
            print yellow('Local branch "' + localBranchName + '" is ahead of remote branch.');

    # On suggere de pusher la(les) branche(s) qui sont seulement sur le local
    if (nbWarnings > 0):
        if (confirm(red('There are many local branches which are ahead of remote branch. Do you want to sync theses?'), default=False)):
            for branchName in pushableBranches:
                local('git push ' + remoteName + ' ' + branchName)
136

137
138
            # Do not alert with diff as local branches are now pushed
            nbWarnings = 0
139

140
141
    # TODO
    # PUSHER LA BRANCHE SUR LE REPO
142

Philippe Mouchel's avatar
Philippe Mouchel committed
143
144
    return nbWarnings

145

146
def _get_local_branches_information():
Samuel Sirois's avatar
Samuel Sirois committed
147
148
    return local('git branch --list -vv', capture=True).splitlines()

149

150
def _get_branch_name(branchRawData):
Samuel Sirois's avatar
Samuel Sirois committed
151
    branchName = branchRawData.replace('*', '').strip()
152
    if (_is_branch_detached(branchName)):
Samuel Sirois's avatar
Samuel Sirois committed
153
154
155
156
        return None
    else:
        return branchName.split()[0]

157

158
def _is_branch_detached(branchName):
Samuel Sirois's avatar
Samuel Sirois committed
159
160
161
162
    pattern = re.compile('\(.*\)')
    return pattern.match(branchName)


163
def _remote_branch_exists(branchName):
Samuel Sirois's avatar
Samuel Sirois committed
164
165
166
    return (len(local('git branch --list --remote "*' + branchName + '"', capture=True).splitlines()) > 0)


167
def _get_files_status_information():
Samuel Sirois's avatar
Samuel Sirois committed
168
    return local('git status -s', capture=True).splitlines()