Posts Tagged ‘Git’
[DevoxxFR 2018] Are you “merge” or “rebase” oriented?
Git, the distributed version control system, has become an indispensable tool in the modern developer’s arsenal, revolutionizing how teams collaborate on code. Its flexibility and power, however, come with a degree of complexity that can initially intimidate newcomers, particularly when it comes to integrating changes from different branches. At Devoxx France 2018, Jonathan Detoeuf, a freelance developer with a passion for Software Craftsmanship and Agile methodologies, tackled one of Git’s most debated topics in his presentation: “T’es plutôt merge ou rebase ?” (Are you more of a merge or rebase person?). He aimed to demystify these two fundamental Git commands, explaining their respective use cases, how to avoid common pitfalls, and ultimately, how to maintain a clean, understandable project history.
Jonathan began by underscoring the importance of the Git log (history) as a “Tower of Babel” – a repository of the team’s collective knowledge, containing not just the current source code but the entire evolution of the project. Well-crafted commit messages and a clear history are crucial for understanding past decisions, tracking down bugs, and onboarding new team members. With this premise, the choice between merging and rebasing becomes more than just a technical preference; it’s about how clearly and effectively a team communicates its development story through its Git history. Jonathan’s talk provided practical guidance, moving beyond the often-unhelpful official Git documentation that offers freedom but little explicit recommendation on when to use which strategy.
Understanding the Basics: Merge vs. Rebase and History
Before diving into specific recommendations, Jonathan Detoeuf revisited the core mechanics of merging and rebasing, emphasizing their impact on project history. A standard merge (often a “merge commit” when branches have diverged) integrates changes from one branch into another by creating a new commit that has two parent commits. This explicitly shows where a feature branch was merged back into a main line, preserving the historical context of parallel development. A fast-forward merge, on the other hand, occurs if the target branch hasn’t diverged; Git simply moves the branch pointer forward. Rebasing, in contrast, re-applies commits from one branch onto the tip of another, creating a linear history as if the changes were made sequentially. This can make the history look cleaner but rewrites it, potentially losing the context of when changes were originally made in relation to other branches.
Jonathan stressed the value of a well-maintained Git history. It’s not just a log of changes but a narrative of the project’s development. Clear commit messages are vital as they convey the intent behind changes. A good history allows for “archaeology” – understanding why a particular piece of code exists or how a bug was introduced, even years later when the original developers are no longer around. Therefore, the decision to merge or rebase should be guided by the desire to create a history that is both accurate and easy to understand. He cautioned that many developers fear losing code with Git, especially during conflict resolution, making it important to master these integration techniques.
The Case for Merging: Durable Branches and Significant Events
Jonathan Detoeuf advocated for using merge commits (specifically, non-fast-forward merges) primarily for integrating “durable” branches or marking significant events in the project’s lifecycle. Durable branches are long-lived branches like main, develop, or release branches. When merging one durable branch into another (e.g., merging a release branch into main), a merge commit clearly signifies this integration point. Similarly, a merge commit is appropriate for marking key milestones such as the completion of a release, the end of a sprint, or a deployment to production. These merge commits act as explicit markers in the history, making it easy to see when major features or versions were incorporated.
He contrasted this with merging minor feature branches where a simple fast-forward merge might be acceptable if the history remains clear, or if a rebase is preferred for a cleaner linear history before the final integration. The key is that the merge commit should add value by highlighting a significant integration point or preserving the context of a substantial piece of work being completed. If it’s just integrating a pull request for a small, self-contained feature that has been reviewed, other strategies like rebase followed by a fast-forward merge, or even “squash and merge,” might be preferable to avoid cluttering the main line history with trivial merge bubbles. Jonathan’s advice leans towards using merge commits judiciously to preserve meaningful historical context, especially for branches that represent a significant body of work or a persistent line of development.
The Case for Rebasing: Feature Branches and Keeping it Clean
Rebasing, according to Jonathan Detoeuf, finds its primary utility when working with local or short-lived feature branches before they are shared or merged into a more permanent branch. When a developer is working on a feature and the main branch (e.g., develop or main) has advanced, rebasing the feature branch onto the latest state of the main branch can help incorporate upstream changes cleanly. This process rewrites the feature branch’s history by applying its commits one by one on top of the new base, resulting in a linear sequence of changes. This makes the feature branch appear as if it was developed sequentially after the latest changes on the main branch, which can simplify the final merge (often allowing a fast-forward merge) and lead to a cleaner, easier-to-read history on the main line.
Jonathan also highlighted git pull –rebase as a way to update a local branch with remote changes, avoiding unnecessary merge commits that can clutter the local history when simply trying to synchronize with colleagues’ work on the same branch. Furthermore, interactive rebase (git rebase -i) is a powerful tool for “cleaning up” the history of a feature branch before creating a pull request or merging. It allows developers to squash multiple work-in-progress commits into more meaningful, atomic commits, edit commit messages, reorder commits, or even remove unwanted ones. This careful curation of a feature branch’s history before integration ensures that the main project history remains coherent and valuable. However, a crucial rule for rebasing is to never rebase a branch that has already been pushed and is being used by others, as rewriting shared history can cause significant problems for collaborators. The decision-making flowchart Jonathan presented often guided towards rebasing for feature branches to integrate changes from a durable branch, or to clean up history before a fast-forward merge.
Best Practices and Conflict Avoidance
Beyond the when-to-merge-vs-rebase dilemma, Jonathan Detoeuf shared several best practices for smoother collaboration and conflict avoidance. Regularly committing small, atomic changes makes it easier to manage history and resolve conflicts if they arise. Communicating with team members about who is working on what can also prevent overlapping efforts on the same files. Structuring the application well, with clear separation of concerns into different files or modules, naturally reduces the likelihood of merge conflicts.
When conflicts do occur, understanding the changes using git diff and carefully resolving them is key. Jonathan also touched upon various Git workflows, such as feature branching, Gitflow, or trunk-based development, noting that the choice of merge/rebase strategy often aligns with the chosen workflow. For instance, the “feature merge” (or GitHub flow) often involves creating a feature branch, working on it, and then merging it back (often via a pull request, which might use a squash merge or a rebase-and-merge strategy depending on team conventions). He ultimately provided a decision tree to help developers choose: for durable branches, merging is generally preferred to integrate other durable branches or significant features. For feature branches, rebasing is often used to incorporate changes from durable branches or to clean up history before a fast-forward merge. The overarching goal is to maintain an informative and clean project history that serves the team well.
Hashtags: #Git #VersionControl #Merge #Rebase #SoftwareDevelopment #DevOps #JonathanDetoeuf #DevoxxFR2018 #GitWorkflow #SourceControl
[DevoxxFR2015] How Git Rescued Our Project (Almost)
Cécilia Bossard, an agile developer at TIM Consulting and co-founder of Women In Technology Nantes, shared a compelling narrative at Devoxx France 2015 about migrating a decade-old software project from SVN to Git. Cécilia detailed how GitFlow streamlined versioning across diverse client deployments, transforming a chaotic process into a manageable one.
Challenges of SVN in a Multi-Version Environment
Cécilia recounted the struggles of managing multiple software versions for clients using SVN. The heterogeneous client base led to complex branching and merging, with the Eclipse SVN plugin causing performance issues due to frequent polling. This inefficiency sparked the team’s decision to explore Git, seeking a more robust solution for their aging product.
This context, Cécilia explained, highlighted SVN’s limitations.
GitFlow’s Streamlined Workflow
Adopting GitFlow, the team established a clear branching model, with feature branches merging seamlessly into development streams. Cécilia shared a developer’s astonishment at completing a day-long merge in minutes, showcasing Git’s efficiency. The migration retained SVN for older versions, porting changes to Git, ensuring continuity while transitioning.
This shift, she noted, revitalized development speed.
Links:
None available
[DevoxxFR2014] Tips and Tricks for Releasing with Maven, Hudson, Artifactory, and Git: Streamlining Software Delivery
Lecturer
Michael Hüttermann, a freelance DevOps consultant from Germany, specializes in optimizing software delivery pipelines. With a background in Java development and continuous integration, he has authored books on Agile ALM and contributes to open-source projects. His expertise lies in integrating tools like Maven, Jenkins (formerly Hudson), Artifactory, andទ
System: ## Git, and Maven to create efficient release processes. His talk at Devoxx France 2014 shares practical strategies for streamlining software releases, drawing on his extensive experience in DevOps consulting.
Abstract
Releasing software with Maven can be a cumbersome process, often fraught with manual steps and configuration challenges, despite Maven’s strengths as a build tool. In this lecture from Devoxx France 2014, Michael Hüttermann presents a comprehensive guide to optimizing the release process by integrating Maven with Hudson (now Jenkins), Artifactory, and Git. He explores the limitations of Maven’s release plugin and offers lightweight alternatives that enhance automation, traceability, and efficiency. Through detailed examples and best practices, Hüttermann demonstrates how to create a robust CI/CD pipeline that leverages version control, binary management, and continuous integration to deliver software reliably. The talk emphasizes practical configurations, common pitfalls, and strategies for achieving seamless releases in modern development workflows.
The Challenges of Maven Releases
Maven is a powerful build tool that simplifies dependency management and build automation, but its release plugin can be rigid and complex. Hüttermann explains that the plugin often requires manual version updates, tagging, and deployment steps, which can disrupt workflows and introduce errors. For example, the mvn release:prepare and mvn release:perform commands automate versioning and tagging, but they lack flexibility for custom workflows and can fail if network issues or repository misconfigurations occur.
Hüttermann advocates for a more integrated approach, combining Maven with Hudson, Artifactory, and Git to create a streamlined pipeline. This integration addresses key challenges: ensuring reproducible builds, managing binary artifacts, and maintaining version control integrity.
Building a CI/CD Pipeline with Hudson
Hudson, now known as Jenkins, serves as the orchestration hub for the release process. Hüttermann describes a multi-stage pipeline that automates building, testing, and deploying Maven projects. A typical Jenkins pipeline might look like this:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git url: 'https://github.com/example/repo.git', branch: 'main'
}
}
stage('Build') {
steps {
sh 'mvn clean package'
}
}
stage('Deploy') {
steps {
sh 'mvn deploy -DskipTests'
}
}
}
}
The pipeline connects to a Git repository, builds the project with Maven, and deploys artifacts to Artifactory. Hüttermann emphasizes the importance of parameterized builds, allowing developers to specify release versions or snapshot flags dynamically.
Leveraging Artifactory for Binary Management
Artifactory, a binary repository manager, plays a critical role in storing and distributing Maven artifacts. Hüttermann highlights its ability to manage snapshots and releases, ensuring traceability and reproducibility. Artifacts are deployed to Artifactory using Maven’s deploy goal:
mvn deploy -DaltDeploymentRepository=artifactory::default::http://artifactory.example.com/releases
This command uploads artifacts to a specified repository, with Artifactory providing metadata for dependency resolution. Hüttermann notes that Artifactory’s cloud-based hosting simplifies access for distributed teams, and its integration with Jenkins via plugins enables automated deployment.
Git Integration for Version Control
Git serves as the version control system, managing source code and enabling release tagging. Hüttermann recommends using Git commit hashes to track builds, ensuring traceability. A typical release process involves creating a tag:
git tag -a v1.0.0 -m "Release 1.0.0"
git push origin v1.0.0
Jenkins’ Git plugin automates checkout and tagging, reducing manual effort. Hüttermann advises using a release branch for stable versions, with snapshots developed on main to maintain a clear workflow.
Streamlining the Release Process
To overcome the limitations of Maven’s release plugin, Hüttermann suggests custom scripts and Jenkins pipelines to automate versioning and deployment. For example, a script to increment version numbers in the pom.xml file can be integrated into the pipeline:
mvn versions:set -DnewVersion=1.0.1
This approach allows fine-grained control over versioning, avoiding the plugin’s rigid conventions. Hüttermann also recommends using Artifactory’s snapshot repositories for development builds, with stable releases moved to release repositories after validation.
Common Pitfalls and Best Practices
Network connectivity issues can disrupt deployments, as Hüttermann experienced during a demo when a Jenkins job failed due to a network outage. He advises configuring retry mechanisms in Jenkins and using Artifactory’s caching to mitigate such issues. Another pitfall is version conflicts in multi-module projects; Hüttermann suggests enforcing consistent versioning across modules with Maven’s versions plugin.
Best practices include maintaining a clean workspace, using Git commit hashes for traceability, and integrating unit tests into the pipeline to ensure quality. Hüttermann also emphasizes the importance of separating source code (stored in Git) from binaries (stored in Artifactory) to maintain a clear distinction between development and deployment artifacts.
Practical Demonstration and Insights
During the lecture, Hüttermann demonstrates a Jenkins pipeline that checks out code from Git, builds a Maven project, and deploys artifacts to Artifactory. The pipeline includes parameters for release candidates and stable versions, showcasing flexibility. He highlights the use of Artifactory’s generic integration, which supports any file type, making it versatile for non-Maven artifacts.
The demo illustrates a three-stage process: building a binary, copying it to a workspace, and deploying it to Artifactory. Despite a network-related failure, Hüttermann uses the opportunity to discuss resilience, recommending offline capabilities and robust error handling.
Broader Implications for DevOps
The integration of Maven, Hudson, Artifactory, and Git aligns with DevOps principles of automation and collaboration. By automating releases, teams reduce manual errors and accelerate delivery, critical for agile development. Hüttermann’s approach supports both small startups and large enterprises, offering scalability through cloud-based Artifactory and Jenkins.
For developers, the talk provides actionable strategies to simplify releases, while organizations benefit from standardized pipelines that ensure compliance and traceability. The emphasis on lightweight processes challenges traditional heavy release cycles, promoting continuous delivery.
Conclusion: A Blueprint for Efficient Releases
Michael Hüttermann’s lecture offers a practical roadmap for streamlining software releases using Maven, Hudson, Artifactory, and Git. By addressing the shortcomings of Maven’s release plugin and leveraging integrated tools, developers can achieve automated, reliable, and efficient release processes. The talk underscores the importance of CI/CD pipelines in modern software engineering, providing a foundation for DevOps success.
Links
[DevoxxFR2014] Git-Deliver: Streamlining Deployment Beyond Java Ecosystems
Lecturer
Arnaud Bétrémieux is a passionate developer with 18 years of experience, including 8 professionally, specializing in open-source technologies, GNU/Linux, and languages like Java, PHP, and Lisp. He works at Key Consulting, providing development, hosting, consulting, and expertise services. Sylvain Veyrié, with nearly a decade in Java platforms, serves as Director of Delivery at Transparency Rights Management, focusing on big data, and has held roles in development, project management, and training at Key Consulting.
Abstract
This article investigates git-deliver, a deployment tool leveraging Git’s integrity guarantees for simple, traceable, and atomic deployments across diverse languages. It dissects the tool’s mechanics, from remote setup to rollback features, and discusses customization via scripts and presets, emphasizing its role in replacing ad-hoc scripts in dynamic language projects.
Core Principles and Setup
Git-deliver emerges as a Bash script extending Git with a “deliver” subcommand, aiming for simplicity, reliability, efficiency, and universality in deployments. Targeting non-Java environments like Node.js, PHP, or Rails, it addresses the pitfalls of custom scripts that introduce risks in traceability and atomicity.
A deployment target equates to a Git remote over SSH. For instance, creating remotes for test and production environments involves commands like git remote add test deliver@test.example.fr:/appli and git remote add prod deliver@example.fr:/appli. Deliveries invoke git deliver <remote> <version>, where version can be a branch, commit SHA, or tag.
On the target server, git-deliver initializes a bare Git repository alongside a “delivered” directory containing clones for each deployment. Each clone includes Git metadata and a working copy checked out to the specified version. Symbolic links, particularly “current,” point to the latest clone, ensuring a fixed path for applications and atomic switches— the link updates instantaneously, avoiding partial states.
Directory names incorporate timestamps and abbreviated SHAs, facilitating quick identification of deployed versions. This structure preserves history, enabling audits and rollbacks.
Information Retrieval and Rollback Mechanisms
To monitor deployments, git-deliver offers a “status” option. Without arguments, it surveys all remotes, reporting the current commit SHA, tag if applicable, deployment timestamp, and deployer. It also verifies integrity, alerting to uncommitted changes that might indicate manual tampering.
Specifying a remote yields a detailed history of all deliveries, including directory identifiers. Additionally, git-deliver auto-tags each deployment in the local repository, annotating with execution logs and optional messages. Pushing these tags to a central repository shares deployment history team-wide.
Rollback supports recovery: git deliver rollback <remote> reverts to the previous version by updating the “current” symlink to the prior clone. For specific versions, provide the directory name. This leverages preserved clones, ensuring exact restoration even if files were altered post-deployment.
Customization and Extensibility
Deployments divide into stages (e.g., init-remote for first-time setup, post-symlink for post-switch actions), allowing user-provided scripts executed at each. For normal deliveries, scripts might install dependencies or migrate databases; for rollbacks, they handle reversals like database adjustments.
To foster reusability, git-deliver introduces “presets”—collections of stage scripts for frameworks like Rails or Flask. Dependencies between presets (e.g., Rails depending on Ruby) enable modular composition. The “init” command copies preset scripts into a .deliver directory at the project root, customizable and versionable via Git.
This extensibility accommodates varied workflows, such as compiling sources on-server for compiled languages, though git-deliver primarily suits interpreted ones.
Broader Impact on Deployment Practices
By harnessing Git’s push mechanics and integrity checks, git-deliver minimizes errors from manual interventions, ensuring deployments are reproducible and auditable. Its atomic nature prevents service disruptions, crucial for production environments.
While not yet supporting distributed deployments natively, scripts can orchestrate multi-server coordination. Future enhancements might incorporate remote groups for parallel pushes.
In production at Key Consulting, git-deliver demonstrates maturity beyond prototyping, offering a lightweight alternative to complex tools, promoting standardized practices across projects.