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..."
Комментариев нет:
Отправить комментарий