Skip to content

Git and Gitlab - Beginners, working as an organized team

In the previous session, you have seen the need for the structuration of a project. This includes organizing its code base and its development workflow, all to at least ease the development by removing or limiting the occurence of conflicts.

Lessons from last session

What went wrong

Whenever a feature was added to the application, merge conflicts occured. It's possible to work this way, but very inefficient, and it grows worst as the project grows (the size of its code base) and the number of contributors grows.

Causes

There are mainly two causes to this inefficiency, and they are both organizational in nature:

  • Code badly structured: a monolithic code, contained in a single file, unmodular
  • Bad team workflow: everybody working on the same branch, constantly rewriting each other

A badly structured code multiplies the risks for merge conflicts, as a simple feature addition or bug correction can span multiple line edits across the code base. Some lines need always to be edited when others are edited as well (coupling). This latter problem can even cause other development (and maintenance) problems down the road.

A wrong choice of team development workflow could also increase the difficulty, and make development less efficient. In this case, it is less a matter of a workflow that is bad in the absolute, but the workflow should be adapted to the size of the project, the team and its experience. In this case, as you were working both on the same branch, and on the same file, you had the worst possible combination.

Note

Eeverybody working on the same branch could be perfectly valid, though it should be reserved for very simple projects of small teams of experienced developers.

Possible Solutions

As the code structure was a big reason for the problems you encountered, a solution would be to refactor the code, restructuring it so the different functionalities are cleanly and clearly separated (modularity). Also the functionalities inside one file should be also modular, organized inside functions that should be as small as possible and focus on a single task or functionality (separation of concerns). This way, a new feature could be as simple as writing a new single python function in the appropriate file, and a bug correction or patch as simple as editing a single existing function.

Now to avoid having a conflict on every push on the Gitlab repository, you could develop on different temporary branches (focusing on one task at a time), keeping the main branch to gather only the fully developed features, and start development from, ensuring it's always clean, functional and the most uptodate. You should also pull often from the main branch, to be sure you are uptodate on your local machine. Even better, we could sanctify the main branch, making its special status formal by protecting it (i.e disabling some git operations, making them only possible through Gitlab). Then every direct push operations on the main branch would be forbidden, using Gitlab Merge Requests to process merges on main though Gitlab UI.

Note

This workflow is often referred to as Github Flow.

It has the advantage to be widely used, flexible and relatively simple and is thus used a lot on Github and more largely for open source projects.

Collaborate cleanly as a team

Starter Code

To make it easier, we are already providing you with a better written refactored version of the smart todo list application code.

main.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from storage import load_todos, save_todos
from commands import add_todo, delete_todo, toggle_done, set_priority, set_due, print_todos

print("Welcome to SmartTodo!")

todos = load_todos()

while True:
    print("\nCommands: add, list, delete, done, priority, due, quit")
    command = input("Enter command: ")

    if command == "add":
        add_todo(todos)
    elif command == "list":
        print_todos(todos)
    elif command == "delete":
        delete_todo(todos)
    elif command == "done":
        toggle_done(todos)
    elif command == "priority":
        set_priority(todos)
    elif command == "due":
        set_due(todos)
    elif command == "quit":
        break
    else:
        print("Unknown command")

    save_todos(todos)

storage.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def load_todos():
    todos = []
    try:
        with open("todos.txt", "r") as f:
            for line in f:
                todos.append(line.strip())
    except:
        pass
    return todos

def save_todos(todos):
    with open("todos.txt", "w") as f:
        for todo in todos:
            f.write(todo + "\n")

commands.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def add_todo(todos):
    # To be implemented by students in Section 3
    pass

def delete_todo(todos):
    n = int(input("Number: "))
    todos.pop(n-1)

def toggle_done(todos):
    n = int(input("Number: "))
    if todos[n-1].startswith("[ ]"):
        todos[n-1] = todos[n-1].replace("[ ]", "[x]", 1)
    else:
        todos[n-1] = todos[n-1].replace("[x]", "[ ]", 1)

def set_priority(todos):
    n = int(input("Number: "))
    p = input("Priority (low/medium/high): ")
    parts = todos[n-1].split('|')
    todos[n-1] = parts[0] + "| priority=" + p + "|" + parts[2]

def set_due(todos):
    n = int(input("Number: "))
    d = input("Due date: ")
    parts = todos[n-1].split('|')
    todos[n-1] = parts[0] + '|' + parts[1] + '| due=' + d

def print_todos(todos):
    print("----- TODOS -----")
    for i in range(len(todos)):
        print(str(i+1) + ". " + todos[i])
    print("-----------------")

Instructions

  1. One group member replace the one main.py file with the new 3 python files. This can be done on the main branch. Don't forget to push!
  2. One group member protects the main branch on Gitlab (allowing Maintainers to merge, allowing no one to push and merge, forbidding force push)

    See Protected Branches On Gitlab.

  3. Every group member can now pull the main branch

  4. Every group member can test the new code: python main.py
  5. Split the list of tasks between each other
  6. For every task, follow this simple workflow

    1. Go on the main branch
    2. Update your main branch by pulling from the Gitlab repository
    3. Create the temporary branch for the task

      Tip

      As a good practice, you should name your temporary branch to reference what it is supposed to implement (the task). For this formation, it could be as simple as naming it feature/TASK_ID with TASK_ID the task number (ex: feature/7)

    4. Go on that temporary branch (reminder: Change branch)

    5. Implement the task
    6. Push your changes (reminder: add - commit - push)
    7. Open a Merge Request on Gitlab for your branch (See Merge requests). You can tick the box to delete the branch after merge is completed so Gitlab will automatically remove your temporary branch after it's merged, keeping the online repository clean.

      Tip

      The source branch is the branch you worked on and pushed, the target branch is the branch on which to merge it (main in this case)

    8. Validate and apply the opened Merge Request (they are all listed in the Merge Requests menu)

Development Tasks

To continue developing the smart todo list application, the following set of tasks will need to be completed.

Task 7 – Add a search Command

Add a new command called search that lets the user search for a word inside todos. Matching todos should be printed.

Example:

1
2
3
Enter command: search
Search word: milk
1. [ ] Buy milk | priority=low | due=none

Tip

This logic belongs in commands.py

Add a new function: search_todos(todos)

Use input(), in, .lower()

Update main.py to recognize the new command

Import the new function

Task 8 – Add Statistics Display

Add a stats command that displays:

  • Total number of todos
  • Number of completed todos
  • Number of incomplete todos

Example:

1
2
3
4
Enter command: stats
Total: 5
Done: 2
Not done: 3

Tip

This logic belongs in commands.py

Add a new function: print_stats(todos)

Completed todos start with [x]

Use startswith(), for loops, counters

Task 9 – Add Tags to Todos

Add a new command called tag that lets users add a tag (e.g. #school, #work) to a todo.

Example:

1
2
3
Enter command: tag
Number: 2
Tag: school

Todo becomes: [ ] Do homework | priority=low | due=none | #school

Tip

This logic belongs in commands.py

Add a new function: add_tag(todos)

You can append " | #tagname" to the todo string

Use string concatenation

Be careful not to erase existing data

Task 10 – Export Todos to a Report File

Add a command called export that saves all todos to a file named report.txt.

Each todo should be on its own line.

Tip

This is file logic → put it in storage.py

Use open(filename, "w"), .write()

Then call this function from main.py

Task 11 – Add a help Command

Add a help command that prints all available commands and what they do.

Example:

1
2
3
4
5
6
7
8
Enter command: help
add      - add a new todo
list     - show all todos
delete   - remove a todo
done     - toggle done
priority - change priority
due      - set due date
...

Tip

This is a simple command → put it in commands.py

Use simple print() statements

No complex logic needed

You might want to take some advance and include the commands added by the other tasks in this session.

Note

There are better ways to maintain a list of commands for an app and have a help command that always include all commands while not needing to be itself modified. It is a python software architecture concern, out of the scope of this formation.

Task 12 – Add Undo (Single-Level)

Add a command undo that restores the previous state of the todo list.

Only one level of undo is required.

Tip

You need to save a copy of the list before each change

Use list.copy()

Store the old version in a variable like previous_todos

Restore it when undo is called

Documentation

Warning

An undocumented project is an unusable project!

Gitlab offers several possibilities to document its projects, from a simple readme file in minimalist text format to a custom website. A README.md file in Markdown format at the root of the project will be displayed directly in Gitlab automatically.

To go further