cvs commit: Up-to-date check failed for `main.c' cvs [commit aborted]: correct above errors first!которое означает, что кто-то уже успел отредактировать этот файл и залить его в CVS раньше вас.
Встает проблема разрешения конфликта. Что делать? Выполнить cvs update и доверить разрешение конфликта CVS? Нет, я кроме себя никому не доверяю. :)
Лично мне в таких случаях всегда хочется видеть перед собой 3 файла - мой, который я только что отредактировал, файл с ревизией BASE и файл с ревизией HEAD, и уже самому, а не в автоматическом режиме, объединить две версии.
Небольшая справка, если вам не знакомы понятия BASE и HEAD ревизии:
BASE - это номер ревизии, которая была скачана вами из CVS, и которую вы впоследствии изменили.
HEAD - это номер самой последней ревизии, находящейся в CVS.
Почему 3 файла, а не только мой и HEAD? Так гораздо нагляднее. Сразу видно, кто какие изменения вносил в код, и гораздо проще понять, как все это объединить так, чтобы не нарушить логику работы программы. К примеру если оба человека одинаковым образом объявят новую переменную в начале функции, то при сравнении этих двух файлов строки, в которых объявлена переменная, будут одинаковыми, и diff их не подсветит. При сравнении же 3 файлов вы сразу заметите, что в обоих файлах появилась новая переменная с одинаковым именем, а значит это сразу будет сигналом для вас, что необходимо тщательно отследить, где она изменяется обоими авторами, чтобы их изменения переменной не пересекались, или вовсе переименовать в одной реализации эту переменную, чтобы явно исключить конфликт.
Исходя из вышеизложенных соображений, я написал скрипт для автоматизации этого процесса. Возможно он пригодится не только мне, поэтому выкладываю его сюда и опишу, как он работает.
Предположим, что вы только что выполнили команду
cvs commitи она выдала вам предупреждение
cvs commit: Up-to-date check failed for `main.c' cvs [commit aborted]: correct above errors first!
Вам нужно запустить мой скрипт, передав ему путь к файлу, конфликтующему с CVS:
cvs_resolve_conflict.sh main.c
Предполагается, что в процессе работы скрипта вы не будете работать с рабочей копией CVS, в которой находится данный файл, т. к. во время своей работы скрипт изменяет файл CVS/Entries из той директории, в которой лежит ваш файл.
После запуска скрипта он копирует ваш файл и все необходимые файлы CVS во временную директорию, чтобы лишний раз не повредить их, и скачивает ревизии BASE и HEAD. Как только все необходимые файлы получены, скрипт запускает программу Meld так, чтобы в левой колонке была ревизия HEAD, в правой - ваша версия, а в центре - ревизия BASE.
С помощью Meld объединять версии очень удобно (спасибо разработчикам!). При сливании перемещайте куски кода из своей версии и ревизии HEAD в ревизию BASE (с краев в центр).
Hint: При объединении кусков кода с помощью Meld очень удобно использовать стрелки. При нажатии на Ctrl количество стрелок увеличивается, что позволяет вставлять код вместо, перед и после существующего кода. Shift тоже имеет специальное значение - с его помощью можно отменять изменения в какой-либо версии.
Если после закрытия Meld скрипт обнаружит, что файл, который был открыт в средней колонке, изменился, он спросит, стоит ли сохранить изменения. Если вы откажетесь от сохранения изменений, то вернетесь к тому состоянию, которое было до запуска скрипта. Если же решите сохранить изменения, то скрипт зайдет в вашу рабочую копию CVS, заменит CVS/Entries новым, в котором прописана версия, конфликт с которой вы уже разрешили, и заменит старый файл тем, который вы только что сформировали в средней колонке при помощи Meld. Теперь вам останется только сделать cvs commit.
Также хочу обратить внимание на то, что если во время работы скрипта кто-то опять зальет новую версию вашего файла, т. е. изменится номер HEAD ревизии, то скрипт сработает нормально. А именно: в таком случае вы разрешите конфликт только со "старым HEAD", и после завершения скрипта при выполнении cvs commit СVS выдаст вам ошибку конфликта "старого HEAD с новым HEAD" которую вы опять сможете разрешить с помощью моего скрипта.
В скрипте установлен обработчик сигналов SIGHUP, SIGINT, SIGQUIT и SIGTERM. Поэтому во время работы скрипта можно его прерывать, например, комбинацией Ctrl+C.
На представленных ниже скриншотах я привел простейший пример разрешения конфликта, чтобы вы смогли оценить удобство использования Meld.
Удачи!



#!/bin/bash
#***************************************************************************
#* Copyright (C) 2008, Konishchev Dmitry *
#* http://konishchevdmitry.blogspot.com/2008/05/cvs.html *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU General Public License as published by *
#* the Free Software Foundation; either version 3 of the License, or *
#* (at your option) any later version. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU General Public License for more details. *
#**************************************************************************/
# Функции -->
cleanup()
{
local tmp_path="/tmp/resolve_cvs_file"
if [[ -e "$temp_dir" ]]
then
# Проверяем на всякий случай то, что мы удаляем.
# Использование rm -r очень опасно!
# Если в скрипте присутствует ошибка при присвоении
# значения переменной temp_dir, то последствия могут
# быть весьма печальными.
if [[ ${temp_dir:0:${#tmp_path}} == $tmp_path ]]
then
rm -r "$temp_dir"
else
echo "Logical error!" >&2
fi
fi
if [[ -e "$resolve_file_dir/$resolve_file_name".bak ]]
then
if [[ -e "$resolve_file_dir/$resolve_file_name" ]]
then
rm "$resolve_file_dir/$resolve_file_name".bak
else
mv "$resolve_file_dir/$resolve_file_name".bak "$resolve_file_dir/$resolve_file_name"
fi
fi
}
die()
{
# "Теги" окрашивания текста -->
local color_start=`echo -e "\033[1;31m"`
local color_end=`echo -e "\033[00m"`
# "Теги" окрашивания текста <--
echo "${color_start}Error! $@$color_end" >&2
cleanup
exit 1
}
interrupt_handler()
{
if [[ "$is_in_critical_section" == "" ]]
then
echo "Script interrupted." >&2
cleanup
exit
fi
}
# Функции <--
# Script start
is_in_critical_section=""
# Проверяем правильность исходных данных -->
resolve_file="$1"
if [[ "$resolve_file" == "" || "$2" != "" ]]
then
die "Usage: resolve_conflict.sh /path/to/file"
fi
if [[ ! -f "$resolve_file" ]]
then
die "File '$resolve_file' not exists."
fi
if [[ -e "$resolve_file".bak ]]
then
die "Please remove '$resolve_file.bak' file."
fi
if [[ -e "$resolve_file".new ]]
then
die "Please remove '$resolve_file.new' file."
fi
# Проверяем правильность исходных данных <--
# Проверяем наличие необходимых приложений -->
which cvs > /dev/null || die "cvs must be installed."
which meld > /dev/null || die "meld must be installed."
# Проверяем наличие необходимых приложений <--
# Получаем родительскую дирректорию файла
# и имя самого файла -->
resolve_file_dir=$(dirname "$resolve_file") || die
cd "$resolve_file_dir" || die
resolve_file_dir="$(pwd)" || die
resolve_file_name=$(basename "$resolve_file") || die
# <--
# Устанавливаем обработчики прерываний -->
trap interrupt_handler SIGHUP
trap interrupt_handler SIGINT
trap interrupt_handler SIGQUIT
trap interrupt_handler SIGTERM
# Устанавливаем обработчики прерываний <--
# Создаем временную директорию
temp_dir=$(mktemp -d /tmp/resolve_cvs_file_XXXXXX) || die
cp -r "$resolve_file_dir"/CVS "$resolve_file_dir/$resolve_file_name" "$temp_dir" || die
cd "$temp_dir" || die
# Получаем во временную директорию все необходимые файлы из CVS -->
# cvs не возвращает кода ошибки, если, например, такого файла не существует,
# поэтому проверяем, появился он или нет.
# Сохраняем резервную копию CVS/Entries
cp CVS/Entries CVS/Entries.bak || die
mv "$resolve_file_name" mine."$resolve_file_name" || die
# BASE -->
cp CVS/Entries.bak CVS/Entries || die
cvs update -r BASE "$resolve_file_name" > /dev/null 2>&1
cp CVS/Entries CVS/Entries.base || die
if [[ ! -e "$resolve_file_name" ]]
then
die "CVS error."
fi
mv "$resolve_file_name" base."$resolve_file_name" || die
# BASE <--
# HEAD -->
cp CVS/Entries.bak CVS/Entries || die
cvs update "$resolve_file_name" > /dev/null 2>&1
cp CVS/Entries CVS/Entries.head || die
if [[ ! -e "$resolve_file_name" ]]
then
die "CVS error."
fi
mv "$resolve_file_name" head."$resolve_file_name" || die
# HEAD <--
# Получаем во временную директорию все необходимые файлы из CVS <--
# Запускаем meld и, если это необходмо, сохраняем изменения в файле -->
resolve_file_mtime=$(stat -c'%y' base."$resolve_file_name") || die
meld head."$resolve_file_name" base."$resolve_file_name" mine."$resolve_file_name" > /dev/null 2>&1
new_resolve_file_mtime=$(stat -c'%y' base."$resolve_file_name") || die
if [[ "$resolve_file_mtime" != "$new_resolve_file_mtime" ]]
then
echo "You had changed file. Do you want to save it? [y/N]"
read user_answer
if [[ "$user_answer" == "y" || "$user_answer" == "Y" ]]
then
# Заменяем старый файл новым, попутно обновляя информацию о нем в CVS -->
# Входим в "критическую секцию", внутри которой скрипт прерывать нельзя
is_in_critical_section="yes"
echo "Saving file..."
cd "$resolve_file_dir" || die
# Создаем бэкап
mv "$resolve_file_name" "$resolve_file_name".bak || die
# На первый взгляд это лишняя операция, т. к. мы могли
# бы сразу переместить файлы из временной папки, а не
# выполнять две операции - копирование и перемещение.
# Но так мы гарантируем, что у нас хватит места для
# наших файлов на диске.
# -->
if ! cp "$temp_dir"/base."$resolve_file_name" "$resolve_file_name".new
then
if [[ -e "$resolve_file_name".new ]]
then
rm "$resolve_file_name".new
fi
die
fi
if ! cp "$temp_dir/CVS/Entries.head" CVS/Entries.new
then
if [[ -e CVS/Entries.new ]]
then
rm CVS/Entries.new
fi
die
fi
# <--
# Делаем бэкап CVS/Entries
mv CVS/Entries CVS/Entries.bak || die
# Обновляем CVS данные о нашем файле
# -->
if ! mv CVS/Entries.new CVS/Entries
then
mv CVS/Entries.bak CVS/Entries \
|| echo "Error! Can't move CVS/Entries.bak to CVS/Entries. \
Please move it manually. Otherwise your CVS sand-box will break."
die
fi
# <--
rm CVS/Entries.bak
# Теперь мы имеем файл такой версии, с которой мы уже разрешили конфликт.
# Поэтому просто заменяем его нашим новым файлом.
mv "$resolve_file_name".new "$resolve_file_name" || die
echo "File '$resolve_file_dir/$resolve_file_name' has been saved."
# Выходим из "критической секции"
is_in_critical_section=""
# Заменяем старый файл новым, попутно обновляя информацию о нем в CVS <--
fi
else
echo "No changes has been applyed to file."
fi
# Запускаем meld и, если это необходмо, сохраняем изменения в файле <--
cleanup
echo "Exiting..."
Комментариев нет:
Отправить комментарий