Hey everyone! Ever wanted to automate tasks within your GitLab projects, like creating new projects, managing users, or fetching issue details? Well, the GitLab API is your secret weapon, and using it with Python makes the whole process a breeze. This article is your comprehensive guide to getting started, covering everything from setting up your environment to crafting cool scripts that streamline your workflow. We'll delve into the core concepts, walk through practical examples, and explore how to troubleshoot common issues. Get ready to level up your GitLab game!
Setting Up Your Python Environment for GitLab API Interaction
Alright, before we dive into the juicy stuff, let's get our environment ready. It's like preparing your workspace before starting a project – essential! You'll need Python installed (version 3.6 or later is recommended), and we'll be using the python-gitlab library. This library is your friendly neighborhood helper, making it super easy to interact with the GitLab API. Think of it as a translator that converts your Python code into API requests and responses.
First things first, make sure Python is installed. You can check this by opening your terminal or command prompt and typing python --version. If it's installed, you'll see the version number. If not, head over to the official Python website (https://www.python.org/downloads/) and download the latest version for your operating system. Installation is usually straightforward – just follow the on-screen instructions.
Next up, we need to install the python-gitlab library. This is a piece of cake, thanks to pip, Python's package installer. Open your terminal or command prompt and type pip install python-gitlab. Pip will download and install the library and its dependencies for you. Once the installation is complete, you're good to go!
To make sure everything's working correctly, you can try importing the library in a Python interpreter. Open a Python interpreter by typing python in your terminal or command prompt, and then type import gitlab. If no error messages appear, you're all set. You can now start using the library to interact with the GitLab API.
Finally, you'll need a GitLab account and a personal access token (PAT). The PAT is like a password that allows your scripts to authenticate with the GitLab API. You can create a PAT in your GitLab account settings under "Profile Settings" -> "Access Tokens". Give your token a descriptive name and select the scopes that your script will need (e.g., api, read_api, write_repository). Remember to keep your PAT safe and secure – don't share it! With your environment set up and your token ready, you're perfectly positioned to start automating your GitLab tasks.
Essential Tools and Libraries
Besides Python itself and the python-gitlab library, a few other tools can be incredibly helpful. Consider using a good text editor or an Integrated Development Environment (IDE) like VS Code, PyCharm, or Sublime Text. These tools provide features like code completion, syntax highlighting, and debugging, which can significantly speed up your development process. Also, familiarizing yourself with Git and version control is crucial, especially when working on projects with multiple people. Tools like Git allow you to track changes to your code, collaborate with others, and revert to previous versions if needed. And don't forget the power of documentation! The official python-gitlab library documentation (https://python-gitlab.readthedocs.io/) is your best friend. It provides detailed information on all the available methods, classes, and parameters.
Authenticating with the GitLab API in Python
Authentication is the key that unlocks the door to the GitLab API. Without it, you won't be able to access or modify any resources. Luckily, the python-gitlab library makes authentication simple and secure. We'll cover two primary methods: using a personal access token (PAT) and using OAuth. For most tasks, using a PAT is the easiest and most common approach. It involves providing your GitLab instance's URL, your personal access token, and, optionally, your user's email address.
Here's a basic example of how to authenticate using a PAT:
import gitlab
# Replace with your GitLab instance URL
GITLAB_URL = 'https://gitlab.com'
# Replace with your personal access token
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
# Initialize the GitLab instance
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Test the authentication
try:
gl.projects.list(get_all=True) # Try to list projects
print("Authentication successful!")
except gitlab.GitlabAuthenticationError:
print("Authentication failed. Check your URL and token.")
except Exception as e:
print(f"An error occurred: {e}")
In this code, we first import the gitlab library. Then, we define the GitLab instance URL and your personal access token. Make sure you replace 'YOUR_PERSONAL_ACCESS_TOKEN' with your actual token. We then initialize the gitlab.Gitlab instance, passing the URL and the token as arguments. The try-except block attempts to list projects, which will verify your authentication. If the authentication is successful, you'll see a "Authentication successful!" message. If not, the except block will catch the GitlabAuthenticationError and print an error message. Always handle authentication errors gracefully to avoid unexpected behavior in your scripts. Proper error handling can save you a lot of debugging time.
OAuth is another authentication method, which is often used for web applications. It involves redirecting the user to GitLab for authentication and obtaining an access token. This method is more complex than using a PAT, but it offers better security and user control. However, this method is useful if you are developing web applications that need to access the GitLab API on behalf of the user. The OAuth setup involves creating an application in GitLab, configuring the redirect URI, and obtaining client ID and client secret. The python-gitlab library supports OAuth authentication, but the setup process requires additional steps that go beyond the scope of this beginner's guide. Always prioritize security when authenticating with the API.
Managing Projects: Creating, Listing, and Deleting
Alright, let's get our hands dirty and start managing projects! Using the python-gitlab library, we can create new projects, list existing ones, and even delete them. This is where automation really shines – imagine creating a bunch of new projects with just a few lines of code.
Creating a New Project
Creating a new project is straightforward. You'll need to specify the project's name and, optionally, other parameters like the visibility level (public, internal, private), a description, and the namespace (group or user). Here's an example:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Create a new project
project_name = 'MyNewProject'
project = gl.projects.create({
'name': project_name,
'visibility': 'private' # Options: private, internal, public
})
print(f"Project '{project_name}' created with ID: {project.id}")
In this example, we create a new project named "MyNewProject" with private visibility. The gl.projects.create() method takes a dictionary of parameters. After creation, the script prints the project ID, which is useful for future operations. The visibility parameter controls who can see your project. 'private' means only project members can see it, 'internal' allows logged-in users to view it, and 'public' makes the project visible to everyone. When you create a project, consider the default settings for the project to match your requirements.
Listing Existing Projects
Listing projects is also simple. You can retrieve a list of all your projects or filter them based on various criteria (e.g., name, visibility). Here's how to list your projects:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# List all projects
projects = gl.projects.list(all=True) # Use all=True to get all projects
for project in projects:
print(f"Project Name: {project.name}, ID: {project.id}")
In this code, gl.projects.list(all=True) retrieves a list of all projects you have access to. The all=True parameter ensures that you get all the projects, not just a limited number. The script then iterates through the list and prints the name and ID of each project. This is super useful if you need to quickly see what projects are available or to find a specific project ID for other operations. You can filter the project list by using additional parameters in the gl.projects.list() method such as search, owned, membership, visibility, etc.
Deleting a Project
Be careful with this one! Deleting a project is a permanent action, so make sure you know what you're doing. Here's how to delete a project:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with the project ID you want to delete
project_id = 12345 # Example project ID
# Delete the project
project = gl.projects.get(project_id)
project.delete()
print(f"Project with ID {project_id} deleted.")
In this example, you need to replace 12345 with the actual ID of the project you want to delete. First, we get the project object using gl.projects.get(project_id). Then, we call the delete() method on the project object. Make sure you have the necessary permissions to delete the project. Deleting a project also deletes all associated data, including repositories, issues, and merge requests. Always double-check that you're deleting the right project, and consider adding confirmation prompts to your script to prevent accidental deletions. You can also use the try-except block to handle potential errors such as a project not found or permission issues.
Working with Issues in GitLab using Python
Issues are the heart of project management in GitLab. Using the python-gitlab library, you can automate issue creation, listing, updating, and more. This can be a huge time-saver for teams that rely heavily on issue tracking.
Creating a New Issue
Creating an issue involves specifying the project ID, a title, and, optionally, other details like a description, labels, and an assignee. Here's how to create an issue:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# Create a new issue
project = gl.projects.get(project_id)
issue = project.issues.create({
'title': 'My New Issue',
'description': 'This is a description of my new issue',
'labels': ['bug', 'urgent']
})
print(f"Issue created with ID: {issue.id}")
First, you need to get the project object. Then, you use project.issues.create() to create the issue. The create() method takes a dictionary of parameters, including the title, description, and labels. Labels help categorize your issues, and they are extremely helpful for organizing and filtering them. To assign a user to an issue, use the assignee_ids parameter, which accepts a list of user IDs. The project_id must be a valid project ID where you have permissions to create issues.
Listing Issues
Listing issues allows you to see all the open issues in a project or filter them based on various criteria. Here’s how you can list issues:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# List all issues for a project
project = gl.projects.get(project_id)
issues = project.issues.list(all=True) # Use all=True to get all issues
for issue in issues:
print(f"Issue ID: {issue.id}, Title: {issue.title}, State: {issue.state}")
This code fetches all issues associated with a specific project, identified by its ID. It then iterates through each issue, printing its ID, title, and current state (e.g., 'open', 'closed'). You can filter issues using parameters like state, labels, assignee_id, etc., to get a more specific list. Using all=True ensures you get all issues, not just a limited number. Filtering issues is super helpful for quickly finding issues that meet specific criteria.
Updating and Closing Issues
Updating an issue allows you to modify its details, such as the description, labels, or status. Closing an issue marks it as resolved. Here’s how to update and close an issue:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# Replace with the issue ID you want to update
issue_id = 123 # Example issue ID
# Get the issue
project = gl.projects.get(project_id)
issue = project.issues.get(issue_id)
# Update the issue (e.g., add a comment, close it)
issue.notes.create({'body': 'This issue has been resolved.'})
issue.state_event = 'close'
issue.save()
print(f"Issue {issue_id} updated and closed.")
First, you get the issue object using project.issues.get(issue_id). Then, you can modify the issue's properties, such as adding a comment using issue.notes.create(). To close the issue, set issue.state_event = 'close' and then call issue.save(). To reopen an issue, set issue.state_event = 'reopen' and save. You can also update other properties of the issue, like the description, labels, and assignees, by modifying the corresponding attributes of the issue object and calling issue.save(). Always remember to call save() to persist your changes.
Dealing with Merge Requests Using the GitLab API in Python
Merge requests are critical to the code review process in GitLab. They allow you to integrate changes from different branches and ensure code quality. With the python-gitlab library, you can automate the creation, listing, and management of merge requests, saving time and improving your workflow.
Creating a Merge Request
Creating a merge request involves specifying the project ID, source and target branches, and, optionally, a title and description. It's like requesting that your code changes be reviewed and merged into the main codebase. Here's how to create a merge request:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# Create a new merge request
project = gl.projects.get(project_id)
merge_request = project.mergerequests.create({
'source_branch': 'feature-branch',
'target_branch': 'main',
'title': 'My New Merge Request',
'description': 'This merge request includes some feature changes'
})
print(f"Merge request created with ID: {merge_request.iid}")
In this example, we specify the source and target branches, along with a title and a description. The source_branch is the branch containing your changes, and target_branch is the branch you want to merge into (e.g., 'main' or 'develop'). The project.mergerequests.create() method creates the merge request. The iid is a unique identifier for the merge request within the project. When creating a merge request, make sure the source_branch exists and contains changes that can be merged into the target_branch.
Listing Merge Requests
Listing merge requests is essential for monitoring your active merge requests and keeping track of their status. Here's how you can list merge requests:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# List all merge requests for a project
project = gl.projects.get(project_id)
merge_requests = project.mergerequests.list(all=True)
for mr in merge_requests:
print(f"Merge Request ID: {mr.iid}, Title: {mr.title}, State: {mr.state}")
This script retrieves a list of all merge requests associated with a project. It then iterates through each merge request, printing its ID, title, and current state (e.g., 'opened', 'merged', 'closed'). The iid is a project-specific identifier. You can also filter the merge requests using various parameters, such as state (e.g., 'opened', 'merged', 'closed'), author_id, assignee_id, etc. This allows you to quickly find merge requests based on different criteria.
Updating and Approving Merge Requests
Updating and approving merge requests allows you to modify their properties and ensure they are merged. Here's how to update and approve a merge request:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# Replace with the merge request ID you want to update (iid)
mr_iid = 123 # Example merge request ID
# Get the merge request
project = gl.projects.get(project_id)
merge_request = project.mergerequests.get(mr_iid)
# Update the merge request (e.g., add a comment)
merge_request.notes.create({'body': 'This merge request has been approved.'})
# Approve the merge request (requires appropriate permissions)
merge_request.approve()
print(f"Merge request {mr_iid} updated and approved.")
First, you get the merge request object using project.mergerequests.get(mr_iid). You can then update its properties, like adding a comment. To approve a merge request, you call the approve() method. Make sure the user authenticating with the API has the necessary permissions to approve the merge request. You might need to change the reviewer or add a label if needed. To merge the changes, you can use the merge() method. Other options include editing the merge request's title, description, and assignees.
Automating CI/CD Pipelines with the GitLab API and Python
GitLab CI/CD (Continuous Integration/Continuous Delivery) pipelines are an integral part of modern software development. Automating your pipelines with the python-gitlab library can drastically improve your workflow, enabling you to trigger pipelines, monitor their status, and manage runners.
Triggering a Pipeline
Triggering a pipeline is a crucial task for automating your build, test, and deployment processes. Here's how you can trigger a pipeline using the API:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# Trigger a new pipeline
project = gl.projects.get(project_id)
pipeline = project.pipelines.create({
'ref': 'main' # or any other branch or tag
})
print(f"Pipeline created with ID: {pipeline.id}")
In this example, we trigger a pipeline for the 'main' branch using project.pipelines.create(). The ref parameter specifies the branch or tag for which the pipeline will be executed. Make sure the branch exists, and the gitlab-ci.yml file is properly configured in your repository. The project.pipelines.create() returns a pipeline object. You can pass other parameters to this function, such as variables to set environment variables for the pipeline. The pipeline will be triggered, and GitLab will start executing the jobs defined in your .gitlab-ci.yml file. This lets you automate your build, test, and deploy processes.
Monitoring Pipeline Status
Monitoring the status of your pipelines is essential for identifying any failures and ensuring your builds and deployments are successful. Here's how you can monitor the status of a pipeline:
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# Replace with the pipeline ID you want to check
pipeline_id = 1234 # Example pipeline ID
# Get the pipeline
project = gl.projects.get(project_id)
pipeline = project.pipelines.get(pipeline_id)
# Get the pipeline status
status = pipeline.status
print(f"Pipeline {pipeline_id} status: {status}")
In this code, we get the pipeline object using project.pipelines.get(pipeline_id). We then access the status attribute of the pipeline object to retrieve its current status (e.g., 'pending', 'running', 'success', 'failed'). You can use the pipeline status to trigger further actions, such as sending notifications or automatically redeploying if the build fails. The status attribute provides the current state of the pipeline, which you can use to check the result of your jobs. Handling pipeline status is very crucial to catch possible errors.
Managing Runners
Runners are the workers that execute your CI/CD jobs. You can manage your runners using the python-gitlab library, including listing runners, enabling/disabling them, and registering new ones. However, this typically requires higher-level permissions on the GitLab instance.
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Example: Listing runners (requires admin or maintainer privileges)
runners = gl.runners.list()
for runner in runners:
print(f"Runner ID: {runner.id}, Description: {runner.description}, Status: {runner.status}")
This is a simple example that lists all the runners available to your GitLab instance. The gl.runners.list() method retrieves a list of all registered runners. You will need admin or maintainer privileges. From the runner object, you can see if the runner is active. By managing runners, you can better manage your CI/CD infrastructure, ensuring your pipelines have the resources they need. With this ability, you can dynamically configure your CI/CD environment.
Advanced Techniques and Error Handling
Let’s go a bit deeper and look at some advanced techniques and important considerations for error handling. These will help you write more robust and reliable scripts.
Working with Project Variables
Project variables are key-value pairs that can be used to store secrets and configuration settings for your projects. You can manage them using the API, allowing you to dynamically configure your pipelines or applications. This could be things like API keys, database credentials, or environment-specific values.
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
# Create a new project variable
project = gl.projects.get(project_id)
variable = project.variables.create({
'key': 'MY_API_KEY',
'value': 'YOUR_ACTUAL_API_KEY',
'protected': True, # Optional: Protect the variable from being changed
'masked': True # Optional: Mask the variable in job logs
})
print(f"Variable created with key: {variable.key}")
In this example, we create a new project variable named 'MY_API_KEY'. protected=True prevents the variable from being modified by non-owners, and masked=True hides the value in the job logs, preventing sensitive information from being exposed. Always remember that using variables securely is critical. Project variables help you avoid hardcoding sensitive information in your pipelines and scripts. You can retrieve project variables within your CI/CD jobs or applications by using the environment variables defined in GitLab.
Handling Rate Limits
GitLab, like most APIs, has rate limits to prevent abuse. If you make too many requests in a short period, your script may start receiving errors. The python-gitlab library provides built-in mechanisms to help you deal with rate limits. Proper handling of rate limits is vital to ensure that your scripts run smoothly.
import gitlab
import time
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Example: Handling rate limits
try:
projects = gl.projects.list(all=True)
for project in projects:
print(f"Project: {project.name}")
except gitlab.GitlabRateLimitError as e:
print(f"Rate limit exceeded. Waiting {e.retry_after} seconds.")
time.sleep(e.retry_after)
# Retry the request
projects = gl.projects.list(all=True)
for project in projects:
print(f"Project: {project.name}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
In this example, we're using a try-except block to catch the GitlabRateLimitError. If a rate limit is hit, the script will print a message, wait for the specified retry_after seconds, and then retry the request. The retry_after attribute of the exception object tells you how long to wait before retrying the request. Always be aware of the rate limits, and implement retry mechanisms in your code to handle them gracefully.
Error Handling and Debugging
Error handling is crucial for writing reliable scripts. You should always anticipate potential errors and handle them gracefully. Use try-except blocks to catch exceptions, such as GitlabAuthenticationError, GitlabError, and other exceptions specific to your script’s logic.
import gitlab
GITLAB_URL = 'https://gitlab.com'
PRIVATE_TOKEN = 'YOUR_PERSONAL_ACCESS_TOKEN'
gl = gitlab.Gitlab(GITLAB_URL, private_token=PRIVATE_TOKEN)
# Replace with your project ID
project_id = 12345
try:
project = gl.projects.get(project_id)
print(f"Project name: {project.name}")
except gitlab.GitlabProjectNotFoundError:
print(f"Project with ID {project_id} not found.")
except gitlab.GitlabAuthenticationError:
print("Authentication failed. Check your URL and token.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Here, we use try-except blocks to handle various exceptions. You should also use logging to record errors and debug your scripts. The log messages can help you understand what went wrong, especially when you are running scripts unattended. For debugging, use print() statements strategically to display the values of variables and the flow of execution. Regularly test your scripts to identify and fix any errors. Handle exceptions gracefully and provide informative error messages to assist with debugging. Using thorough error handling makes your scripts much more robust and easier to maintain.
Best Practices and Security Considerations
Let’s wrap up with some best practices and security considerations to ensure your scripts are both effective and secure. Security should always be a top priority when working with APIs.
Securely Storing API Credentials
Never hardcode your personal access token (PAT) directly into your scripts. This is a massive security risk. Instead, store your credentials securely. Use environment variables, configuration files, or a dedicated secrets management tool like HashiCorp Vault or AWS Secrets Manager. For example, you can set the PRIVATE_TOKEN environment variable and retrieve it in your script using os.environ.get('PRIVATE_TOKEN'). This way, your token won't be visible in your code, which makes your code more secure and easier to manage.
Code Organization and Version Control
Organize your code into modular functions and classes. This improves readability and maintainability. Version control with Git is a must. Use a Git repository to track changes to your scripts, collaborate with others, and revert to previous versions if needed. This allows you to track changes to your code, collaborate with others, and revert to previous versions if necessary.
Input Validation and Sanitization
Always validate and sanitize user inputs to prevent security vulnerabilities, such as injection attacks. If your script takes input from users, make sure to validate the input to ensure it is in the expected format and within the expected range. Sanitize your inputs to prevent any malicious code from being executed.
Documentation and Testing
Document your code thoroughly. Include comments to explain what your code does, how it works, and why. Write unit tests to ensure your code is working correctly. Create unit tests for your functions and classes to verify that they work as expected. Testing helps ensure that your code is behaving correctly and that any changes you make don't break existing functionality.
Conclusion: Automate and Simplify with the GitLab API and Python
So there you have it, guys! We've covered a lot of ground, from setting up your Python environment and authenticating with the GitLab API to managing projects, issues, merge requests, and even CI/CD pipelines. You're now equipped with the knowledge and tools to automate many of your GitLab tasks, saving you time and boosting your productivity. Remember to focus on security, organize your code, and always handle errors gracefully. The python-gitlab library is a powerful ally in this automation journey. With the power of Python and the flexibility of the GitLab API, you can streamline your workflows, automate repetitive tasks, and ultimately, become a more efficient and effective user of GitLab. So go out there, start experimenting, and have fun automating your GitLab projects! Happy coding!
Lastest News
-
-
Related News
Sniper Action Movies: A Deep Dive
Jhon Lennon - Oct 23, 2025 33 Views -
Related News
Kia EV6 Vs. Hyundai IONIQ 5: Which Electric SUV Wins?
Jhon Lennon - Oct 23, 2025 53 Views -
Related News
Lakers Vs Kings: Reliving The Epic 2000 Showdown
Jhon Lennon - Oct 31, 2025 48 Views -
Related News
BBC Premier League Soccer News
Jhon Lennon - Oct 23, 2025 30 Views -
Related News
BA Arabic Distance Education: Find Courses Near You
Jhon Lennon - Oct 30, 2025 51 Views